Swift中计算属性的KVO

14

在Swift中,是否可以对计算属性使用KVO?

var width = 0
var height = 0

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

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

如果客户端代码修改宽度或高度,是否会触发observeValueForKeyPath?

在进行重构之前,请先确认一下。假设有人已经知道答案,那么即使使用playground,KVO的语法也非常令人讨厌。

(我假设答案是“不会”)


KVO(键值观察)的重点在于setter,@dhomes,你的setter在哪里? - Basheer_CAD
1
请查看RxSwift - dfrib
@Basheer_CAD 这就是重点!我在问能否观察只读计算属性?设置宽度或高度会触发新的区域计算吗?(我认为不会,但也许有一些花哨的编译器???) - David Homes
@dfri,非常有趣!非常感谢! - David Homes
@dhomes 看看 Rob 的回答,应该会有帮助。 - Basheer_CAD
3个回答

24

那段代码不起作用有两个原因:

  1. 《使用Swift与Cocoa和Objective-C》中“键-值观察”一节所述,您必须向area属性添加dynamic属性。

  2. 键-值观察编程指南中的“注册依赖关系键”所述(适用于Objective-C和Swift),您必须声明area依赖于widthheight。为使其生效,您还需将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])

1
根据同样的文档链接,我相信你应该指定上下文,对吧? - Mark A. Donohoe
3
通常使用“上下文”有很多好处,但这个答案不是关于KVO的那部分。此外,如果您只从NSObject继承,除非您想要,否则不需要使用“上下文”,因为NSObject不会注册任何KVO通知,所以不可能与您的超类发生冲突。 - rob mayoff
好信息!谢谢分享! - Mark A. Donohoe

2

为了避免使用未检查的字符串属性名称而产生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
    }
}

2
如@Rob在他的答案中所述,将area设置为dynamic,以便从objective-c中观察。
现在为widthheight属性添加willSet { }didSet { },在两个属性的willSet中添加self.willChangeValueForKey("area"),在didSet中添加self.didChangeValueForKey("area"); 现在,每当宽度或高度改变时,area的观察者都会收到通知。
注意:此代码未经过测试,但我认为它应该做到预期的效果。

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