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"