23

I have a class that needs to call out to a delegate when one of its properties changes. Here are the simplified class and protocol for the delegate:

protocol MyClassDelegate: class { func valueChanged(myClass: MyClass) } class MyClass { weak var delegate: MyClassDelegate? var currentValue: Int { didSet { if let actualDelegate = delegate { actualDelegate.valueChanged(self) } } } init(initialValue: Int) { currentValue = initialValue } } 

This all works just fine. But, I want to make this class generic. So, I tried this:

protocol MyClassDelegate: class { func valueChanged(genericClass: MyClass) } class MyClass<T> { weak var delegate: MyClassDelegate? var currentValue: T { didSet { if let actualDelegate = delegate { actualDelegate.valueChanged(self) } } } init(initialValue: T) { currentValue = initialValue } } 

This throws two compiler errors. First, the line declaring valueChanged in the protocol gives: Reference to generic type 'MyClass' requires arguments in <...>. Second, the call to valueChanged in the didSet watcher throws: 'MyClassDelegate' does not have a member named 'valueChanged'.

I thought using a typealias would solve the problem:

protocol MyClassDelegate: class { typealias MyClassValueType func valueChanged(genericClass: MyClass<MyClassValueType>) } class MyClass<T> { weak var delegate: MyClassDelegate? var currentValue: T { didSet { if let actualDelegate = delegate { actualDelegate.valueChanged(self) } } } init(initialValue: T) { currentValue = initialValue } } 

I seem to be on the right path, but I still have two compiler errors. The second error from above remains, as well as a new one on the line declaring the delegate property of MyClass: Protocol 'MyClassDelegate' can only be used as a generic constraint because it has Self or associated type requirements.

Is there any way to accomplish this?

3 Answers 3

53

It is hard to know what the best solution is to your problem without having more information, but one possible solution is to change your protocol declaration to this:

protocol MyClassDelegate: class { func valueChanged<T>(genericClass: MyClass<T>) } 

That removes the need for a typealias in the protocol and should resolve the error messages that you've been getting.

Part of the reason why I'm not sure if this is the best solution for you is because I don't know how or where the valueChanged function is called, and so I don't know if it is practical to add a generic parameter to that function. If this solution doesn't work, post a comment.

Sign up to request clarification or add additional context in comments.

2 Comments

For what it's worth, valueChanged is called in the above code in the didSet watcher on currentValue.
Great answer. I was able to create a delegate for my generic class. I have a further question. How can I distunguish which's object's generic delegate is calling the generic delegate function? thing1 or thing2? Please see this gist. gist.github.com/zakkhoyt/0b2543bb5a5995b41e73bef83391c378
5

Protocols can have type requirements but cannot be generic; and protocols with type requirements can be used as generic constraints, but they cannot be used to type values. Because of this, you won't be able to reference your protocol type from your generic class if you go this path.

If your delegation protocol is very simple (like one or two methods), you can accept closures instead of a protocol object:

class MyClass<T> { var valueChanged: (MyClass<T>) -> Void } class Delegate { func valueChanged(obj: MyClass<Int>) { print("object changed") } } let d = Delegate() let x = MyClass<Int>() x.valueChanged = d.valueChanged 

You can extend the concept to a struct holding a bunch of closures:

class MyClass<T> { var delegate: PseudoProtocol<T> } struct PseudoProtocol<T> { var valueWillChange: (MyClass<T>) -> Bool var valueDidChange: (MyClass<T>) -> Void } 

Be extra careful with memory management, though, because blocks have a strong reference to the object that they refer to. In contrast, delegates are typically weak references to avoid cycles.

1 Comment

Works perfectly, and thanks for the idea of passing closures. Roman's answer came first and solves the problem I'm having more simply, however.
4

You can use templates methods with type erasure...

protocol HeavyDelegate : class { func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R } class Heavy<P, R> { typealias Param = P typealias Return = R weak var delegate : HeavyDelegate? func inject(p : P) -> R? { if delegate != nil { return delegate?.heavy(self, shouldReturn: p) } return nil } func callMe(r : Return) { } } class Delegate : HeavyDelegate { typealias H = Heavy<(Int, String), String> func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R { let h = heavy as! H // Compile gives warning but still works! h.callMe("Hello") print("Invoked") return "Hello" as! R } } let heavy = Heavy<(Int, String), String>() let delegate = Delegate() heavy.delegate = delegate heavy.inject((5, "alive")) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.