We're creating an abstract ViewController superclass (ABSViewController) with some refactored callback functionality. So it contains a property for the callback function like (VCArg)->Void (sends the callback with myself as the argument). But, this class is never actually used since concrete subclasses are created in my application (MyVieController). Is there a way in ABSViewController to declare the closure argument as "Self" so that the client will have the correct class in the callback on the way out?
Example code:
class ABSViewController<R,I> : UICollectionViewController {
var successCallbackBlock:((Self)->Void)? // This does not work, but I want it to!
}
class MyViewController : ABSViewController {
func someMethodThatIsNotInABSViewController() {}
}
class MyClient {
let vc = MyCollectionViewController()
vc.successCallbackBlock = { (bVC) in
bVC.someMethodThatIsNotInABSViewController()
}
}
class Base {
var successCallbackBlock: ((Self) -> Void)?
}
class Sub1: Base {
func doSub1Things()
}
class Sub2: Base {}
let sub1 = Sub1()
let sub2 = Sub2()
sub1.successCallbackBlock = { $0.doSub1Things() }
let base1: Base = sub1
let base2: Base = sub2
base2.successCallbackBlock = base1.successCallbackBlock
sub2.successCallbackBlock(sub2) // uh oh!
Actually you can do this, you just need to make ABSViewController a protocol instead of a base class.
Ideally, you'd want to be able to specify it as protocol ABSViewController: UICollectionViewController but that isn't quite supported yet (although it is coming), so you have to do this:
protocol ABSCallbackProtocol: AnyObject {
var successCallbackBlock: ((Self) -> Void)? { get set }
}
typealias ABSViewController = UICollectionViewController & ABSCallbackProtocol
// If you can, make classes final.
final class MyViewController: ABSViewController {
var successCallbackBlock: ((MyViewController) -> Void)?
func someMethodThatIsNotInABSViewController() {}
}
final class MyClient {
let vc = MyViewController()
vs.successsCallbackBlock = { vc in
vc.someMethodThatIsNotInABSViewController()
}
}
The compiler will keep you from using the protocol as a type when it has Self in its requirements. But you said you never actually need to use the base class as a type, so that's not a problem for your use case.
Protocol 'CallbackProtocol' requirement 'successCallbackBlock' cannot be satisfied by a non-final class ('ABSCollectionViewController<R, I>') because it uses 'Self' in a non-parameter, non-result type position
So it looks like I can't create an abstract superclass with factored out functionality, thus killing the original idea.
If you declare all the variables you need in a protocol, then you can write all default behavior you need in a protocol extension. No abstract base class needed. Do you have any examples of the behavior you're trying to inherit?
and I want them to still be overrideable in the subclass if necessary.
a) I don't know if implementing these in an protocol-satisfying extension will properly register them with the ObjC runtime so that they will get called.
b) I don't believe you can override protocol extension methods in a subclass, can you?
c) Besides which, I don't think I want to then have implementation of these methods on all UICollectionViewControllers.
That is trickier. You can implement these in an extension, but the problem is that they're already implemented by UICollectionViewController and Swift doesn't currently let you override in an extension. The best you can do is to implement this extension:
At this point, it's a lot of machinery to avoid doing a single cast of an argument in a block. I think I'll just stick with the simple way. Thanks for the insights - I'm sure it will all come in handy later.
Right. Neither does Objective-C, but it's still a useful concept. It doesn't really require strict compiler support, and you can do it by convention. An abstract superclass is just a class that we all agree isn't useful to instantiate.
Yes, it's easy to workaround by just having any methods that are required to be overridden trap if the default is called. But truthfully, the only time I had to do something like that was to work around the lack of generalized existentials.
protocol MyProtocol {
associatedtype MyType
}
func takesMyProtocol(_ p: MyProtocol) { } // Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements