如果了解过设计模式的同学,应该都知道有一种设计模式叫做观察者模式,属于行为型模式,即当对象存在一对多的依赖关系,当一个对象发生变化时,需要自动通知它的依赖对象。通常用于实时事件处理。
我们来研究一下 iOS
里对观察者模式的支持,即 KVO(key-value observing)
,键值对观察,其原理是基于 KVC(key-value-coding)
和 runtime
。通过 Swfit 研究。
KVO 使用
由于 Swift4
之后 KVO
的 api
有所改变,所以先来看看 Swift4
之前使用 KVO
。
class Test: NSObject { dynamic var field = "field"}复制代码
var test = Test()override func viewDidLoad() { super.viewDidLoad() test.addObserver(self, forKeyPath: "field", options: [.new, .initial], context: nil) test.field = "change"}override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { print(change)}deinit { test.removeObserver(self, forKeyPath: "field")}复制代码
在Swift4之前使用 KVO
,即需要在 deinit
中调用 removeObserver
,否则会crash,还需要重写 NSObject
的
func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)复制代码
以完成更多的操作。
而在Swift4中,KVO
的 API 变得友好很多。
@objcMembers class Test: NSObject { dynamic var field = "field"}var observer: NSKeyValueObservation! override func viewDidLoad() { super.viewDidLoad() let test = Test() observer = test.observe(\.field, options: [.new, .initial]) { (object, change) in print(change) } test.field = "change" }复制代码
通过闭包优化了 KVO
的实现,在官方WWDC视频上What's New in Foundation专题上介绍 KVO
时,表示该函数会返回一个 NSKeyValueObservation
,所以只需要管理这个实例的生命周期,不再需要移除观察者,再也不用担心忘记移除观察者而导致crash。(ps: 可以尝试在方法内声明 NSKeyValueObservation
对象,可以发现即使改变了属性值也不会调用闭包内的操作。 因为随着方法的结束,这个实例和闭包的生命周期都结束了。)
在闭包中第一个参数是对被观察者的引用,防止在闭包内使用被观察者而导致循环引用的问题(相当nice)。
遗憾的是只有继承于 NSObject
的对象才能够使用 KVO
。
KVO 原理
Swift 4 之前
采用断点调试
addObserver
之前 addObserver
之后 可见,在 addObserver
之后, test
实例会将isa指针指向 NSKVONotifying_Test
的派生类
再通过runtime来具体看看这个两个类的区别
采用辅助函数,查看test
类和方法的变化。 func find() { print(NSStringFromClass(object_getClass(test)!)) print(String(describing: class_getName(object_getClass(test)))) var count: UInt32 = 0 let methodlist = class_copyMethodList(object_getClass(test), &count) for i in 0..
addObserver
之前 addObserver
之后 从上图输出结果可见,在 addObserver
之后类发生了改变,并且添加了一个私有属性 _isKVOA
,从名字可以推测是用于对类标示,以此来标示是 KVO
。
从图中可以看出 NSKVONotifying_Test
重写了被观察属性 field
的 set
方法(即 setField:
)。再来看看具体是怎么重写的。
根据 KVO
的 api
有手动调用的方法。
func willChangeValue(forKey key: String)func didChangeValue(forKey key: String)复制代码
可以推测是在 set
方法内添加 func willChangeValue(forKey key: String)
和 func didChangeValue(forKey key: String)
通过堆栈信息具体看一看调用情况。
第一次调用时 initial
,第二次是属性发生变化时调用的。从堆栈信息一目了然。
Swift 4 之后
接下来我们来探讨下,Swift4
之后 KVO
的新 API
,具体的底层原理。 先根据上面的方法测试下是否是通过 runtime
新增 NSKVONotifying_Test
派生类实现的。
从测试结果可见,与 Swift4
之前的原理一致。但是区别在于 NSKVONotifying_Test
的生命周期由 NSKeyValueObservation
管理,通过断点调试看看 NSKeyValueObservation
。
NSKeyValueObservation
内有一个 object
属性 是指向观察者 test
和 callback
回调闭包,以及 path
代指被观察的属性。 从堆栈信息,可以一目了然的看到当被观察属性发生改变时,调用情况。 NSKeyValueObservation
作为了观察者和消息转发者,接收通知和通知 test
的属性发生改变,从而调用 闭包
内的具体操作。
才疏学浅,如有什么理解不到位的欢迎指出。