介绍
Swift 4.0 增加了新的 KVO 方式, 代码大大减少,并且不再需要手动 removeObserver
先来一个例子看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class MyObjectToObserve: NSObject { @objc dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
class MyObserver: NSObject { @objc var objectToObserve: MyObjectToObserve var observation: NSKeyValueObservation? init(object: MyObjectToObserve) { objectToObserve = object super.init() observation = observe(\.objectToObserve.myDate) { object, change in print("Observed a change to \(object.objectToObserve).myDate, updated to: \(object.objectToObserve.myDate)") } } } let observed = MyObjectToObserve() let observer = MyObserver(object: observed) observed.updateDate()
|
以上就是官方实例,可以看到,代码比较以前减少了很多,使用也更加方便
但是,由于 KVO 是 OC 下的特性,所以只有继承 NSObject
的子类才可以使用 KVO
,并且被观察的属性需要用 dynamic
修饰
还有需要注意的是,虽然不需要手动 removeObserver
,但也导致观察的闭包没有强引用,当前作用域结束时,就会被释放,所以需要我们手动强引用,保证闭包不被收回
实现
Swift 代码是开源的,所以我们可以看一下 Observe 是如何实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public func observe<Value>( _ keyPath: KeyPath<Self, Value>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void) -> NSKeyValueObservation { let result = NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath) { (obj, change) in let notification = NSKeyValueObservedChange(kind: change.kind, newValue: change.newValue as? Value, oldValue: change.oldValue as? Value, indexes: change.indexes, isPrior: change.isPrior) changeHandler(obj as! Self, notification) } result.start(options) return result }
|
这个方法定义了三个参数,分别是 keyPath
options
changeHandler
,最后返回了一个 NSKeyValueObservation
- keyPath 是 Swift 4 新增的
KeyPath
类型,与 Swift 3 中的 #KeyPath
不同,它更加安全,性能更好
- options 可选参数,默认传入空数组
- changeHandler 当观察属性发生改变时调用的回调
其中最关键的就是 NSKeyValueObservation,通过它实现了自释放
NSKeyValueObservation
下面是这个类的内容,并不复杂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class NSKeyValueObservation : NSObject { weak var object : NSObject? let callback : (NSObject, NSKeyValueObservedChange<Any>) -> Void let path : String static var swizzler : NSKeyValueObservation? = { let bridgeClass: AnyClass = NSKeyValueObservation.self let observeSel = #selector(NSObject.observeValue(forKeyPath:of:change:context:)) let swapSel = #selector(NSKeyValueObservation._swizzle_me_observeValue(forKeyPath:of:change:context:)) let rootObserveImpl = class_getInstanceMethod(bridgeClass, observeSel) let swapObserveImpl = class_getInstanceMethod(bridgeClass, swapSel) method_exchangeImplementations(rootObserveImpl, swapObserveImpl) return nil }() fileprivate init(object: NSObject, keyPath: AnyKeyPath, callback: @escaping (NSObject, NSKeyValueObservedChange<Any>) -> Void) { path = _bridgeKeyPathToString(keyPath) let _ = NSKeyValueObservation.swizzler self.object = object self.callback = callback } fileprivate func start(_ options: NSKeyValueObservingOptions) { object?.addObserver(self, forKeyPath: path, options: options, context: nil) } public func invalidate() { object?.removeObserver(self, forKeyPath: path, context: nil) object = nil } @objc func _swizzle_me_observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSString : Any]?, context: UnsafeMutableRawPointer?) { guard let ourObject = self.object, object as? NSObject == ourObject, let change = change else { return } let rawKind:UInt = change[NSKeyValueChangeKey.kindKey.rawValue as NSString] as! UInt let kind = NSKeyValueChange(rawValue: rawKind)! let notification = NSKeyValueObservedChange(kind: kind, newValue: change[NSKeyValueChangeKey.newKey.rawValue as NSString], oldValue: change[NSKeyValueChangeKey.oldKey.rawValue as NSString], indexes: change[NSKeyValueChangeKey.indexesKey.rawValue as NSString] as! IndexSet?, isPrior: change[NSKeyValueChangeKey.notificationIsPriorKey.rawValue as NSString] as? Bool ?? false) callback(ourObject, notification) } deinit { object?.removeObserver(self, forKeyPath: path, context: nil) } }
|
这个类很简单,可以很清除的了解到它做了什么,可以看出,它其实是把原本的 KVO 做了一层封装
它负责了 KVO 的生命周期,当手动停止或者生命周期结束时,会自动 removeObserver
,并且把监听事件作为一个闭包传进来,避免了之前复杂的写法
感觉对这个类,与 OC 中 KVOController 库的实现十分类似,可以通过学习 KVOController 来了解这一思想
相关链接
Smart KeyPaths
Swift Github - Observe
Key-Value Observing
KVOController