Swift 的计算属性上的 KVO

KVO on Swift's computed properties

在 Swift 中,计算 属性 上的 KVO 是否可行?

var width = 0
var height = 0

private var area : Double {
    get {
        return with * height
    }
}

self.addOberser(self, forKeyPath: "area", ......

修改 with 或 height 的客户端代码会触发 observeValueForKeyPath 吗?

只是在进行市长 class 重构之前进行检查。如果有人手头有答案,KVO 的语法很烦人,甚至不值得去游乐场。 (我假设答案是否定的)

该代码无效有两个原因:

  1. 您必须将 dynamic 属性添加到 area 属性,如 “Adopting Cocoa Design Patterns” in Using Swift with Cocoa and Objective-C 下的“键值观察”部分所述.

  2. 您必须声明 area 依赖于 widthheight,如 “Registering Dependent Keys” in the Key-Value Observing Programming Guide 中所述。 (这适用于 Objective-C 和 Swift。)为此,您还必须将 dynamic 添加到 widthheight.

    (您可以在 widthheight 更改时调用 willChangeValueForKeydidChangeValueForKey,但通常更容易实现 keyPathsForValuesAffectingArea。)

因此:

import Foundation

class MyObject: NSObject {

    @objc dynamic var width: Double = 0
    @objc dynamic var height: Double = 0

    @objc dynamic private var area: Double {
        return width * height
    }

    @objc class func keyPathsForValuesAffectingArea() -> Set<String> {
        return [ "width", "height" ]
    }

    func register() {
        self.addObserver(self, forKeyPath: "area", options: [ .old, .new ], context: nil)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print("observed \(keyPath) \(change)")
    }
}

let object = MyObject()
object.register()
object.width = 20
object.height = 5

输出:

observed Optional("area") Optional([__C.NSKeyValueChangeKey(_rawValue: new): 0, __C.NSKeyValueChangeKey(_rawValue: kind): 1, __C.NSKeyValueChangeKey(_rawValue: old): 0])
observed Optional("area") Optional([__C.NSKeyValueChangeKey(_rawValue: new): 100, __C.NSKeyValueChangeKey(_rawValue: kind): 1, __C.NSKeyValueChangeKey(_rawValue: old): 0])

正如@Rob 在他的回答中所说,从 objective-c

观察 area dynamic

现在为 widthheight 属性添加 willSet { }didSet { }
willSet 内为两个属性添加此 self.willChangeValueForKey("area") 并在 didSet 中添加 self.didChangeValueForKey("area");

现在每次宽度或高度变化时都会通知 area 的观察者。

注意:此代码未经测试,但我认为它应该符合预期

为了避免 willChangeValueForKeydidChangeValueForKey 使用未经检查的字符串 属性 名称,我最近经常做的是计算 属性 private(set) 并创建一个私人 func 更新它。

import Foundation

class Foo: NSObject {

    @objc dynamic private(set) var area: Double = 0
    @objc dynamic var width: Double = 0 { didSet { updateArea() } }
    @objc dynamic var height: Double = 0 { didSet { updateArea() } }

    private func updateArea() {
        area = width * height
    }
}