Kyle: “Is it possible to have class Foo: Bar where Foo has a delegate of type protocol FooDelegate: BarDelegate where Bar also has a delegate declared as type BarDelegate? In my case i am subclassing scrollview and want to declare delegate as my own type that conforms to UIScrollViewDelegate? I get the error that a property delegate with type FooDelegate? cannot override a property with type UIScrollviewDelegate?”
If I’m understanding, you want to be able to create delegation where the same property (delegate
) can be assigned to ever more specialized protocols when subclassing. So, for example, you might have a base class like UIScrollView whose delegate is UIScrollViewDelegate, and a subclass like UITableView whose delegate is UITableViewDelegate. Right?
In Swift, you must ensure the child protocol conforms to the parent protocol. I do not believe is the case with the scroll and table view delegates in Objective C. Start with a core delegate protocol like this.
public protocol DelegateProtocol: class { func showMyType() -> Void }
It’s an empty placeholder for all delegation protocols. You don’t need the showMyType
requirement here. I’m just putting it in for demonstration, so you can check where the required member is implemented. If you want to use weak
delegation, you must declare class
, as in this example.
To demonstrate how this works, here are a core delegate protocol and a derived one:
public protocol CoreTypeDelegate: DelegateProtocol {} extension CoreTypeDelegate { public func showMyType() { print ("This is a Core Type Delegate (required)") } public func shared() { print ("Shared at Core level (extension)") } } public protocol DerivedTypeDelegate: CoreTypeDelegate {} extension DerivedTypeDelegate { public func showMyType() { print ("This is a Derived Type Delegate (required)") } public func shared() { print ("Override at Derived level (extension)") } public func exclusive() { print ("Implemented only at Derived level (extension)") } } // Implement one of each class ACoreDelegate: CoreTypeDelegate {} // like UIScrollViewDelegate class ADerivedDelegate: ACoreDelegate, DerivedTypeDelegate {} // like UITableViewDelegate print("-- Core delegate") let myCoreDelegate = ACoreDelegate() myCoreDelegate.shared() // core version myCoreDelegate.showMyType() // core version print("-- Derived delegate") let myDerivedDelegate = ADerivedDelegate() myDerivedDelegate.shared() // derived override myDerivedDelegate.showMyType() // derived version myDerivedDelegate.exclusive() // only in derived
These three methods differentiate how required members, extension-only members, and exclusive-members are accessed when instances are used in different roles.
Next, here’s a basic protocol for classes that use delegates:
public protocol Delegatable { associatedtype DelegateType: DelegateProtocol var delegate: DelegateType? { get set } }
This protocol consists of a conforming type declaration, and a delegate property. This indirection allows you to store instances of arbitrary types, so you can use the same delegate
property for both a base class and its more specialized children.
To see this in action, you need delegatable types . Here’s a base class, similar to the role that scroll views play. (Warning: weak delegation will crash playgrounds. If you’d rather use a playground, omit weak
in the property implementation.)
// Like UIScrollView public class BaseClass<Delegate: CoreTypeDelegate>: Delegatable { public typealias DelegateType = Delegate public weak var delegate: DelegateType? = nil public func baseFunc() { print("implemented in base class") } }
Now you can create an instance, specifying a base class for delegation:
var myBaseInstance = BaseClass<ACoreDelegate>() myBaseInstance.delegate = myCoreDelegate // ok print("-- Should use core implementations") myBaseInstance.delegate?.showMyType() myBaseInstance.delegate?.shared()
The derived delegate class inherits from the base delegate class. You can substitute it in, but the instance will still use core implementations.
print("-- Conformance by derived delegate type") print("is CoreTypeDelegate", myDerivedDelegate is CoreTypeDelegate) // true print("is DerivedTypeDelegate", myDerivedDelegate is DerivedTypeDelegate) // true print("is ACoreDelegate", myDerivedDelegate is ACoreDelegate) // true print("is ADerivedDelegate", myDerivedDelegate is ADerivedDelegate) // true print("-- Will still use core implementations, because it's typed to CoreTypeDelegate") myBaseInstance.delegate = myDerivedDelegate // ok myBaseInstance.delegate?.showMyType() myBaseInstance.delegate?.shared() print("-- Casting delegate, only required implementation does not override") guard let derived = myBaseInstance.delegate as? DerivedTypeDelegate else { fatalError("Cannot cast derived delegate to DerivedTypeDelegate") } derived.showMyType() // still uses core showme derived.shared() // uses derived shared derived.exclusive() // not available in core
Subclassing the base class and creating instances with the derived delegate protocol enables you to use the delegate
property with a more specialized feature set.
// Like UITableView public class DerivedClass<Delegate: DerivedTypeDelegate>: BaseClass { public func derivedFunc() { print("implemented in derived class") } } var myDerivedInstance = DerivedClass<ADerivedDelegate>() // myDerivedInstance.delegate = myCoreDelegate // no, does not allow, cannot assign value of type 'ACoreDelegate' to type 'ADerivedDelegate?' myDerivedInstance.delegate = myDerivedDelegate // yes print("-- Should use native implementations for all") myDerivedInstance.delegate?.showMyType() // yes! myDerivedInstance.delegate?.shared() myDerivedInstance.delegate?.exclusive()
As always, I’ve probably messed up some things along the way, so if you have better solutions or you find an issue in this example, please let me know and I’ll fix. Thanks!
2 Comments
Probably a typo : I think DerivedClass should inherit from BaseClass (not “BaseClass” alone).
Plus : how would you cope with the additional complexity that we often want the delegate methods to take an instance of the “Delegatable” class as first argument ?
e.g : scrollViewDidScroll(_ scrollView: UIScrollView)
I couldn’t come up with something smarter than having BaseClass implement some BaseClassProtocol (resp. DerivedClass implement some DerivedClassProtocol), and declare that methods of the CoreTypeDelegate (resp. DerivedTypeDelegate) require a BaseClassProtocol instance (resp. DerivedClassProtocol instance) as first argument.
“DerivedClass should inherit from BaseClass-of-Delegate” (brackets have been removed from my comment 🙂 )