Hello all.
This is my first post on Swift Forums.
I don't know if a proposal that addresses the similar problem already exists or not, so please forgive me if that's the case.
Introduction
Add intermediate
keyword to subclass definition to mark that the actual direct superclass is unknown when the subclass is defined.
Motivation
It's not uncommon when developing an iOS app to define base view controller subclasses that you want all the view controllers in the project to inherit from.
class BaseViewController: UIViewController {
var myInt: Int
var myString: String
override func viewDidLoad() {
super.viewDidLoad()
// Do extra work that is common for view controllers in this app
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Do extra work that is common for view controllers in this app
}
}
Now, soon you'll find that you also need to write a subclass of UITableViewController
too.
So the naive approach would be to copy the code above and change the class name and the class it inherits from.
class BaseTableViewController: UITableViewController {
var myInt: Int
var myString: String
override func viewDidLoad() {
super.viewDidLoad()
// Do extra work that is common for view controllers in this app
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Do extra work that is common for view controllers in this app
}
}
But what about UICollectionViewController
, UINavigationController
, UITabBarController
, UIPageViewController
or even view controllers that don't yet exist.
The code above will have to be repeated again and again.
You can already see how painful this could become.
And when we change one part of the code, we have to make sure all of them are properly synchronized.
Of course, we can use protocol extension to ease this situation, but we still have to override methods to forward the work to the extension.
And if we add a new overriden method, says viewWillLayoutSubviews()
, we'll have to update all the subclasses to forward the work too.
Also, since we cannot define instant variables in the extension, it can become quite tedious when we want to have private instant variables to keep track of the internal state.
Proposed solution
We add a new intermediate
keyword to the subclass definition.
intermediate class IntermediateBaseViewController: UIViewController {
var myInt: Int
var myString: String
override func viewDidLoad() {
super.viewDidLoad()
// Do extra work that is common for view controllers in this app
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Do extra work that is common for view controllers in this app
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Do extra work that is common for view controllers in this app
}
}
This will mark that this subclass's actual direct superclass is unknown at the time it's defined.
We can write the code for this subclass as if it's just a normal direct subclass of UIViewController
.
But like protocol, we cannot instantiate an object from this subclass.
Now we add the following code to define all the subclasses that we want to inherit from different type of view controllers.
We add the actual subclass of UIViewController
that we want IntermediateBaseViewController
to actually inherits from in the parentheses.
class BaseViewController: IntermediateBaseViewController(UIViewController) { }
class BaseTableViewController: IntermediateBaseViewController(UITableViewController) { }
class BaseCollectionViewController: IntermediateBaseViewController(UICollectionViewController) { }
The result would be the same as if the code below is generated by the compiler:
class IntermediateBaseViewController_UIViewController: UIViewController {
// copied all the code from the IntermediateBaseViewController's declaration
}
class BaseViewController: IntermediateBaseViewController_UIViewController { }
class IntermediateBaseViewController_UITableViewController: UITableViewController {
// copied all the code from the IntermediateBaseViewController's declaration
}
class BaseTableViewController: IntermediateBaseViewController_UITableViewController { }
class IntermediateBaseViewController_UICollectionViewController: UICollectionViewController {
// copied all the code from the IntermediateBaseViewController's declaration
}
class BaseCollectionViewController: IntermediateBaseViewController_UICollectionViewController { }
And of course, each of the subclasses are free to add additional methods or instant variables.
They can also, call methods and use instant variables defined in IntermediateBaseViewController
.
They can even override the methods that're defined in IntermediateBaseViewController
since it's not different from overriding any methods from superclass.
class BaseViewController: IntermediateBaseViewController(UIViewController) {
var myDouble: Double
func myMethod() {
// ..
}
func override viewDidLoad() {
super.viewDidLoad()
// Do additional work
}
}
Additional consideration
We can make intermediate
subclass able to conform to protocols, and all the subclasses would conform to those protocols as well.
class RootClass {
// ...
}
class SubclassOne: RootClass {
// ...
}
protocol MyProtocol {
// ...
}
intermediate class IntermediateBaseClass: RootClass, MyProtocol {
// Add method to conform to MyProtocol
}
class SubclassTwo: IntermediateBaseClass(RootClass) { }
class SubclassThree: IntermediateBaseClass(SubclassOne) { }
In this case SubclassTwo
and SubclassThree
will conform to MyProtocol
without additional work because its superclass has already adopted that protocol.
Source compatibility
This is a additive proposal with no source breaking changes.
Effect on ABI stability
My knowledge on Swift compiler is limited, but I think if the compiler can generate the code explained above, there shouldn't be any problems with ABI stability.