Generic Delegates in Swift
June 16, 2016
Recently, I stumbled over a problem as I wanted to integrate a delegate with generic function definitions. I created a protocol with an associatedType
to make it generic. The protocol (simplified) looked basically like this:
protocol StackDelegate {
associatedtype T
func didPushElement(element: T, stack: Stack<T>)
}
I simply wanted to add the delegate property to my receiving object,
struct Stack<T> {
var delegate : StackDelegate?
...
}
but then I found myself fighting with the following error:
Protocol 'StackDelegate' can only be used as a generic
constraint because it has Self or associated type
requirements
It turns out that when using a generic protocol in this way, the compiler can’t infer a concrete type during compilation. I found a great article that explains this problem in depth: Generic Protocols & Their Shortcomings.
One way to solve this issue is to use a small helper struct called thunk, that acts as bridge between the delegate and the receiving object:
struct StackDelegateThunk<T> : StackDelegate {
private let _didPushElement : (T, Stack<T>) -> Void
init<P:StackDelegate where P.T == T>(_ delegate: P) {
_didPushElement = delegate.didPushElement
}
func didPushElement(element: T, stack: Stack<T>) {
_didPushElement(element, stack)
}
}
It forwards the calls from the receiving object to the delegate. „Using this, we can effectively erase our abstract generic protocol types in favor of another concrete, more fully-fledged type. This is often referred to as type erasure.” 1
Having the struct in place, the delegate declaration can replaced with the following lines:
struct Stack<T> {
private(set) var delegate : StackDelegateThunk<T>?
mutating func setDelegate<P:StackDelegate where P.T == T>(delegate: P) {
self.delegate = StackDelegateThunk<T>(delegate)
}
...
}
That’s basically it. A playground with a full example can be found here.