为类创建通用委托

3

假设我有一个非常简单的类:

class Box<T> {
  var boxedObject:T

  init(object: T) {
    self.boxedObject = object
  }
}

我现在想要实现的是添加委托,以便通知我文本框中的值已更改:
protocol BoxDelegate<T>: class {
    func valueInBoxChanged(box: Box<T>) -> Void
}

class Box<T> {
    var boxedObject: T {
        didSet {
            self.delegate?.valueInBoxChanged(self)
        }
    }
    weak var delegate: BoxDelegate<T>?

    init(object: T) {
        self.boxedObject = object
    }
}

这段代码当然不能工作,因为我们没有通用的委托。我可以将委托作为一个带有闭包的结构体,但这是一个有点丑陋的解决方案。在Swift中,我应该如何处理这样的事情?


在这种特定情况下,如果委托只实现了一个方法,我建议将泛型类型声明直接移动到该方法中。当然,如果协议声明了多个方法,这样做就不太优雅了,但在这种简单的情况下,这几乎是一样的。 - Rich Tolley
2个回答

1
假设你有一个类,而不是 BoxDelegate,我们称之为 Listener:
public typealias ValueChanged <T> = (T) -> Void
public class Listener<T> {
    var valueChanged: ValueChanged<T>

    public init(_ valueChanged: @escaping ValueChanged<T>) {
        self.valueChanged = valueChanged
    }
}


"

Box" 可以这样写:

"
class Box<T> {
    // the listener needs to be weak in order to be deallocated when the that is
    // observing the changes is deallocated
    weak private(set) var listener: Listener<T>?
    init(_ listener: Listener<T>) {
        self.listener = listener
    }
}

现在,您可以为需要观察其状态的属性创建包装器类:

class Observed<T> {
    var listeners: [Box<T>] = []
    private let queue: DispatchQueue = .main
    var value: T {
        didSet {
            notifyListeners()
        }
    }
    func notifyListeners() {
        notifyListeners(value)
    }
    
    func notifyListeners(_ value: T) {
        queue.async { [weak self] in
            self?.listeners.forEach { (box) in
                box.listener?.valueChanged(value)
            }
        }
    }
    func bind(_ listener: Listener<T>) {
        listeners.append(Box(listener))
    }
    
    func bind(_ listener: @escaping ValueChanged<T>) -> Listener<T> {
        let listener = Listener(listener)
        listeners.append(Box(listener))
        return listener
    }
    init(_ value: T) {
        self.value = value
    }
}

甚至还可以使用属性包装器:

@propertyWrapper
struct Bind<T> {
    let observable: Observed<T>
    init(wrappedValue: T) {
        observable = Observed(wrappedValue)
    }
    
    var wrappedValue: T {
        get {
            observable.value
        }
        set {
            observable.value = newValue
        }
    }
    
    var projectedValue: Observed<T> {
        observable
    }
}

使用方法:

var text = ""
class ViewModel {
    @Bind
    var isOn: Bool = false
}

let viewModel = ViewModel()

let bond = viewModel.$isOn.bind { isOn in
    if isOn {
        text = "on"
    } else {
        text = "off"
    }
    
}
viewModel.isOn = true
RunLoop.main.run(until: Date().advanced(by: 0.1))
text
viewModel.isOn = false
RunLoop.main.run(until: Date().advanced(by: 0.1))
text

In a playground


0
由于Swift中的类型约束协议,您可能无法完成上述尝试。您可以在设置委托时创建一个闭包,并稍后调用该闭包。
以下是使您的情况在某种程度上更简单的方法:
protocol BoxDelegate: class {
    associatedtype T
    func valueInBoxChanged(box: Box<T>) -> Void
}

class Box<T> {

    var notifyClosure: (Void -> Void)?

    var boxedObject: T {
        didSet {
            self.notifyClosure?()
        }
    }

    func setBoxDelegate<M where M:BoxDelegate, M.T == T>(delegate: M) {
        notifyClosure = {
            delegate.valueInBoxChanged(self)
        }
    }

    init(object: T) {
        self.boxedObject = object
    }
}

所以,您不需要添加额外的结构体和闭包来使其工作。相反,您不会为BoxDelegate创建属性,而是创建一个方法来设置它,并基于此创建一个闭包对象。

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