Swift KVO

Swift 中的 KVO(键值观察)是一种用于观察对象属性变化的机制。当被观察的属性发生变化时,会通知观察者。在 Swift 中使用 KVO,需要类继承自 NSObject,并且需要使用 @objc dynamic 修饰要观察的属性。

KVO 的优缺点

优点

  • 实现了松耦合的对象间通信。
  • 可以方便地观察属性变化并作出反应。

缺点

  • 语法较为复杂且容易出错,特别是在添加和移除观察者时。
  • 不能用于非 NSObject 类型的对象。
  • 性能开销较大,尤其是在频繁变化的属性上。

KVO 和 KVC 的关系

KVC(键值编码)允许通过字符串键访问对象的属性,而 KVO 则允许观察这些属性的变化。KVO 的实现依赖于 KVC,通过 KVC 访问属性的变化来触发观察者通知。

如何避免 KVO 引起的崩溃

  • 确保在对象释放之前移除所有观察者
  • 使用 NSKeyValueObservingOptions 设置适当的观察选项,避免不必要的性能开销。
  • 使用 context 参数来区分不同的 KVO 观察者,防止混淆。

如何手动触发 KVO

被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用 willChangeValue 和 didChangeValue 方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。

示例代码

class MyObject: NSObject { @objc dynamic var myProperty: String = "" func manuallyTrigger() { willChangeValue(forKey: #keyPath(myProperty)) didChangeValue(forKey: #keyPath(myProperty)) } } class MyObserver: NSObject { var object: MyObject init(object: MyObject) { self.object = object super.init() self.object.addObserver(self, forKeyPath: #keyPath(MyObject.myProperty), options: [.new, .old], context: nil) } deinit { self.object.removeObserver(self, forKeyPath: #keyPath(MyObject.myProperty)) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == #keyPath(MyObject.myProperty) { if let newValue = change?[.newKey] as? String, let oldValue = change?[.oldKey] as? String { print("Property changed from \(oldValue) to \(newValue)") } } } } let obj = MyObject() let observer = MyObserver(object: obj) obj.myProperty = "New Value" obj.manuallyTrigger()


自实现可观察对象

class ObservableObject<T> { var value: T? { didSet { self.listeners?.forEach({ $0(value) }) } } init(_ value: T?) { self.value = value } var listeners: [((T?)->Void)]? = [] func bind(_ listener: @escaping (T?)->Void) { listener(value) self.listeners?.append(listener) } } let observableObject: ObservableObject<String> = ObservableObject("test") observableObject.bind { value in print(value) } observableObject.value = "123" observableObject.value = "3" observableObject.value = "1"