Swift delegate for a generic class

后端 未结 3 1376
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-02-05 09: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 MyC         


        
相关标签:
3条回答
  • 2021-02-05 09:32

    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.

    0 讨论(0)
  • 2021-02-05 09:37

    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.

    0 讨论(0)
  • 2021-02-05 09:45

    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"))
    
    0 讨论(0)
提交回复
热议问题