Delegate of Generic Type

Say I have a generic class that I want to have a delegate, but some delegate callbacks use the the placeholder type.

class Dummy<T> {
    protocol DummyDelegate {
        func process(_: T)
    }
    weak var delegate: Delegate?
}

The problem is that Swift doesn’t allow for protocol inside generic type, and protocol itself doesn’t use generic (they use associatetype instead).

So instead I use Any in place of T:

protocol DummyDelegate: class {
    func process(_: Any)
}
class Dummy<T> {
    weak var delegate: DummyDelegate?
}

Is there a way to avoid Any?
I could add Delegate as another placeholder for Dummy but then it’d forgo the flexibility provided by delegate pattern.

Not at a computer right now, but in your last example, couldn’t you make the DummyDelegate protocol generic too, and then when declaring the weak var delegate then bind that to T?

With generic protocols currently absent from the language, there’s no good option for generic delegates right now. You can declare closure properties instead of a delegate, which uses the generic type in its signature, if you want to maintain the type safety.

The actual delegate I’m using has quite a few functions right now (and I’m adding even more). So moving from delegate to closure might not be very maintainable.

In that case DummyDelegate will protocol with associatedtype, which can’t be a type on it’s own, so one needs to adjust Dummy to something like this

class Dummy<T, Delegate> where Delegate: DummyDelegate, Delegate.T == T {
    weak var delegate: Delegate?
}

Which is not ideal. I can hardly change delegate to anything else, and it’s even harder to create Dummy without delegate.

You can type-erase the delegate while hiding that from the callee if you so choose. From the client's perspective, the API appears very similar to a non-generic delegate pattern.

protocol DummyDelegate: AnyObject {
    associatedtype T
    func process(_: T)
}

class AnyDummyDelegate<T>: DummyDelegate {
    private var _process: (T) -> Void

    init<D: DummyDelegate>(_ delegate: D) where D.T == T {
        _process = { [weak delegate] in delegate?.process($0) }
    }

    func process(_ t: T) {
        _process(t)
    }
}

class Dummy<T> {
    private var delegate: AnyDummyDelegate<T>?

    func setDelegate<D: DummyDelegate>(_ delegate: D) where D.T == T {
        self.delegate = AnyDummyDelegate(delegate)
    }
}

Note that the weak capturing of the delegate has moved from the class to the type-erased delegate.

3 Likes

It is also possible to create a type-erasing wrapper that does not use closures, as explained here or here or here or…

Type-erasing wrapper
protocol Foo {
  associatedtype Bar
  var x: Bar { get set }
}


private class _AnyFooBase<Bar>: Foo {
  var x: Bar {
    get { fatalError("Must override") }
    set { fatalError("Must override") }
  }
  
  init() {
    guard type(of: self) != _AnyFooBase.self else {
      fatalError("AnyFooBase cannot be instantiated, use a subclass")
    }
  }
}

private final class _AnyFooBox<T: Foo>: _AnyFooBase<T.Bar> {
  var wrapped: T
  
  init(_ t: T) { wrapped = t }
  
  override var x: T.Bar {
    get { return wrapped.x }
    set { wrapped.x = newValue }
  }
}


final class AnyFoo<Bar>: Foo {
  private let box: _AnyFooBase<Bar>
  
  init<T: Foo>(_ foo: T) where T.Bar == Bar {
    box = _AnyFooBox(foo)
  }
  
  var x: Bar {
    get { return box.x }
    set { box.x = newValue }
  }
}
1 Like

Oh yeah, sorry - my brain must be on Christmas break too. :-/