【问题标题】:Swift delegate for a generic class通用类的 Swift 委托
【发布时间】:2015-02-19 19:18:49
【问题描述】:

我有一个类需要在其属性之一更改时调用委托。以下是委托的简化类和协议:

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
    }
}

这一切都很好。但是,我想让这个类通用。所以,我尝试了这个:

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
    }
}

这会引发两个编译器错误。首先,在协议中声明valueChanged 的行给出:Reference to generic type 'MyClass' requires arguments in &lt;...&gt;。其次,在didSet 观察者中对valueChanged 的调用会抛出:'MyClassDelegate' does not have a member named 'valueChanged'

我认为使用typealias 可以解决问题:

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
    }
}

我似乎在正确的道路上,但我仍然有两个编译器错误。上面的第二个错误仍然存​​在,以及声明MyClassdelegate 属性的行上的一个新错误:Protocol 'MyClassDelegate' can only be used as a generic constraint because it has Self or associated type requirements

有没有办法做到这一点?

【问题讨论】:

    标签: swift generics


    【解决方案1】:

    如果没有更多信息,很难知道解决问题的最佳方案是什么,但一种可能的解决方案是将您的协议声明更改为:

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

    这消除了协议中对typealias 的需要,并且应该可以解决您收到的错误消息。

    我不确定这是否是您的最佳解决方案的部分原因是因为我不知道valueChanged 函数的调用方式或位置,所以我不知道它是否实用向该函数添加通用参数。如果此解决方案不起作用,请发表评论。

    【讨论】:

    • 对于它的价值,valueChangedcurrentValue 上的didSet 观察程序的上述代码中被调用。
    • 很好的答案。我能够为我的泛型类创建一个委托。我还有一个问题。如何区分哪个对象的泛型委托正在调用泛型委托函数?东西1还是东西2?请看这个要点。 gist.github.com/zakkhoyt/0b2543bb5a5995b41e73bef83391c378
    【解决方案2】:

    您可以使用带有类型擦除的模板方法...

    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"))
    

    【讨论】:

      【解决方案3】:

      协议可以有类型要求,但不能是通用的;和具有类型要求的协议可以用作通用约束,但不能用于类型值。因此,如果您走这条路,您将无法从泛型类中引用您的协议类型。

      如果您的委托协议非常简单(例如一两个方法),您可以接受闭包而不是协议对象:

      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
      

      你可以将这个概念扩展到一个包含一堆闭包的结构:

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

      不过,在内存管理方面要格外小心,因为块对它们所引用的对象具有强引用。相反,委托通常是弱引用以避免循环。

      【讨论】:

      • 完美运行,感谢传递闭包的想法。然而,罗曼的回答首先解决了我遇到的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-16
      • 1970-01-01
      • 1970-01-01
      • 2015-10-28
      • 2014-12-07
      相关资源
      最近更新 更多