一个通用类的Swift委托

22

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

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的行会给出:引用泛型类型'MyClass'需要在<...>中指定参数。其次,在didSet观察器中调用valueChanged会抛出:'MyClassDelegate'没有名为'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 属性的行上出现了一个新错误:协议'MyClassDelegate'只能用作泛型约束,因为它具有Self或关联类型要求
有没有办法解决这个问题?
3个回答

52

如果没有更多信息,很难确定如何解决您的问题,但一个可能的解决方案是将您的协议声明更改为以下内容:

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

这消除了协议中需要typealias的需求,并应解决您一直在收到的错误消息。

我不确定这是否是对您最好的解决方案的部分原因是我不知道在何处或如何调用valueChanged函数,因此我不知道向该函数添加一个通用参数是否实际可行。如果此解决方案不起作用,请发表评论。


就此而言,valueChanged 在上述代码中是在 currentValuedidSet 观察器中被调用的。 - GarlicFries
很好的答案。我已经能够为我的泛型类创建一个委托。我还有一个问题。如何区分是哪个对象的泛型委托在调用泛型委托函数?thing1还是thing2?请参见此代码片段。https://gist.github.com/zakkhoyt/0b2543bb5a5995b41e73bef83391c378 - VaporwareWolf

5

协议可以有类型要求,但不能是泛型的;具有类型要求的协议可以用作泛型约束条件,但不能用来为值类型。因此,如果您选择这种方法,将无法从通用类中引用您的协议类型。

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

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
}

在处理内存管理时要特别小心,因为块会对它们所引用的对象有强引用。相比之下,委托通常是弱引用以避免循环引用。


运行完美,感谢传递闭包的想法。罗曼的答案先到并更简单地解决了我遇到的问题。 - GarlicFries

4
您可以使用类型擦除与模板方法...
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"))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接