Generic Delegate Protocols
This one requires a bit of setup, but exposes a cool pattern by the end, if you’ll bear with me. Let’s say you have a custom struct
that you want to hold a generic value:
struct MyStruct<T>{
var value:T
}
Great! Now lets say you want to add a delegate that does something when the value changes. First step is to define the delegate protocol:
protocol MyStructDelegateProtocol{
func valueDidChange(value:???)
}
Remember that our value is generic, so we don’t know it’s type ahead of time. It’d be nice if protocols accepted generic params like so:
//Does not work!
protocol MyStructDelegateProtocol<T>{
func valueDidChange(value:T)
}
No good. Ok, you know what would be even better? Nested protocols! Any types you need would be in scope, and the enclosing type (which is the thing that requires the protocol to begin with) acts as a namespace. This would be awesome. But it also doesn’t work:
//Also doesn't work.
//But if it did, it'd be amazing!
struct MyStruct<T>{
protocol DelegateProtocol{
func valueDidChange(value:T)
}
var value:T
}
class MyClass:MyStruct.DelegateProtocol{
//implement stuff.
}
Maybe in Swift 2.0. In the meantime, we have to use associated types as our way of communicating generic types to our protocols:
protocol MyStructDelegateProtocol{
//Declare associated type:
typealias ValueType
func valueDidChange(value:ValueType)
}
class MyClass:MyStructDelegateProtocol{
//Assign associated type a concrete value:
typealias ValueType = String
let myStruct:MyStruct<ValueType>?
func valueDidChange(value: ValueType) {
//knows value is a String
}
}
Alright! Now we just add a delegate property to our struct
and we’re set:
//Error? Wut?
struct MyStruct<T>{
var value:T
var delegate:MyStructDelegateProtocol
}
Uh oh. Remember that associated type?
error: protocol ‘MyStructDelegateProtocol’ can only be used as a generic constraint because it has Self or associated type requirements
Stupid associated type requirements! So MyClass
tells MyStruct
what type the value
has. And it defines the associated type for our protocol. But MyStruct
doesn’t know what the associated type of the protocol is, and we have no way to tell it1. What do we do?
Exactly what the error says; instead of using MyStructDelegateProtocol
as a type for our delegate, let’s use it to qualify the generic we pass in. Then it can be the source of value
’s type and the type of our delegate property:
struct MyStruct<T:MyStructDelegateProtocol>{
var value:T.ValueType
var delegate:T
}
protocol MyStructDelegateProtocol{
typealias ValueType
func valueDidChange(value:ValueType)
}
class MyClass:MyStructDelegateProtocol{
typealias ValueType = String
let myStruct:MyStruct<MyClass>?
func valueDidChange(value: ValueType) {
//deal with delegate.
}
}
In the example above, we implement our delegate protocol in MyClass
, so that’s the type we give MyStruct
’s generic type param. This is still far from ideal, primarily because it requires we have a type that implements MyStructDelegateProtocol
2. But for those (rare?) cases when we need a property to have a type that happens to be a protocol with associated types, this can be a real lifesaver.
1. Assigning a concrete type to the protocol’s associated type in MyStruct
won’t work because MyStruct
is merely declaring something has a type of MyStructDelegateProtocol
, not implementing the protocol itself.↩
2. Because if we don’t, our associated type never gets declared and we have nothing to pass for MyStruct
’s generic parameter.↩