Blog

Generic Delegate's in Swift

A common pattern in cocoa development is the use of the delegate pattern. Most commonly used as a callback mechanism.

In Swift however sometimes we would like to use this pattern along with a generic implementation of our type. First, lets look at the problem.

The Problem

A typical approach would look like this:

struct Item { }

protocol ItemDelegate: class {
    func didSelect(item: Item)
}

final class ItemController {
    weak var delegate: ItemDelegate?
}

This works great while Item is a concrete type, but what if we wanted to make Item generic?

protocol Item { }
struct MyItem: Item { }

final class ItemController<ItemType: Item> {
    weak var delegate: ItemDelegate?
}

Well we can simply make our delegate generic as well right?

protocol ItemDelegate: class {
    func didSelect<ItemType: Item>(item: ItemType)
}

Well actually this isn't right. To see the problem lets check out the callsite:

let controller = ItemController<MyItem>()
func didSelect<ItemType>(item: ItemType) where ItemType : Item {
    print("\(item)")
}

Specifically where ItemType : Item. Item is our protocol and not our concrete type MyItem. So even though we've successfully constrained our controller to a specific type, our delegate is much less specific.

The Solution

What we really want to do is define an associatedtype on our delegate.

protocol ItemDelegate {
    associatedtype ItemType: Item
    func didSelect(item: ItemType)
}

The problem is that now we have a generic constraint on our ItemDelegate. The solution is actually quite simple. Instead of constraining our ItemController to a specific Item, instead constrain it to a specific ItemDelegate.

Our final solution would look like the following:

protocol ItemDelegate: class {
    associatedtype ItemType: Item
    func didSelect(item: ItemType)
}

final class ItemController<Delegate: ItemDelegate> {
    weak var delegate: Delegate?
    func addItem(item: Delegate.ItemType)
}

Now if we look at our callsite:

let controller = ItemController<MyItem>()
func didSelect(item: MyItem) {
    print("\(item.name)")
}

Recap

By moving the generic constraint to the delegate we can now safely define an instance of ItemController and ensure our delegate callbacks are type-safe to the same concrete instance.

This is a fairly simple approach but makes working with generic delegate's much simpler and much safer.