Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift delegate for a generic class

Tags:

generics

swift

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?

like image 593
GarlicFries Avatar asked Feb 19 '15 19:02

GarlicFries


People also ask

Can delegates be used with generic types?

Delegates defined within a generic class can use the generic class type parameters in the same way that class methods do. Generic delegates are especially useful in defining events based on the typical design pattern because the sender argument can be strongly typed and no longer has to be cast to and from Object.

How do you define generic delegates?

You can define a Generic delegate with type parameters. delegate T numbermanipulat<T>(T var); T is the generic type parameter that allows you to specify an arbitrary type (T) to a method at compile time without specifying a collection type in the method or class declaration.

How do I use delegates in Swift?

Key Steps to DelegationCreate a delegate protocol that defines the messages sent to the delegate. Create a delegate property in the delegating class to keep track of the delegate. Adopt and implement the delegate protocol in the delegate class. Call the delegate from the delegating object.


3 Answers

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.

like image 189
Aaron Rasmussen Avatar answered Oct 09 '22 17:10

Aaron Rasmussen


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"))
like image 42
Dsjove Avatar answered Oct 09 '22 17:10

Dsjove


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.

like image 4
zneak Avatar answered Oct 09 '22 15:10

zneak