KVO
全称KeyValueObserving
,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO
的实现机制,所以对属性才会发生作用,一般继承自NSObject
的对象都默认支持KVO
。
KVO
和NSNotificationCenter
都是iOS
中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO
是一对一的,而不一对多的。KVO
对被监听对象无侵入性,不需要修改其内部代码即可实现监听。
实现原理
KVO
是通过isa-swizzling
技术实现的(这句话是整个KVO
实现的重点)。在运行时根据原类创建一个中间类(NSKVONotifying_xxx
),这个中间类是原类的子类,并动态修改当前对象的isa
指向中间类。并且将class
方法重写,返回原类的Class
。所以苹果建议在开发中不应该依赖isa
指针,而是通过class
实例方法来获取对象类型。
KVO
会重写keyPath
对应属性的setter
方法,没有被KVO
的属性则不会重写其setter
方法。在重写的setter
方法中,修改值之前会调用willChangeValueForKey:
方法,修改值之后会调用didChangeValueForKey:
方法,这两个方法最终都会被调用到observeValueForKeyPath:ofObject:change:context:
方法中。
实现过程
- 某个类(Cat)的属性在第一次被观察时,runtime会动态的创建一个Cat类的子类,名为
NSKVONotifying_Cat
,然后重写所有Cat类中被观察属性的Set方法,然后通过NSKVONotifying_Cat
类中重写的Set方法实现属性改变的通知. - 每个类对象都会有一个isa指针指向当前类,当我们第一次观察这个类时,原类Cat的isa指针就会指向其派生类
NSKVONotifying_Cat
,那么此时我们调用原类对象的set方法其实就是调用的派生类NSKVONotifying_Cat
的set方法. - 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey:和 didChangeValueForKey:在一个被观察属性发生改变之前 willChangeValueForKey:一定会被调用这就 会记录旧的值。而当改变发生后didChangeValueForKey:会被调用继而 observeValueForKey:ofObject:change:context:也会被调用。
- KVO的这套实现机制中苹果还偷偷重写了class方法让我们误认为还是使用的当前类从而达到隐藏生成的派生类
综上所述,其
可变容器的监听
例如对Cat类的某个可变容器属性监听之后如果直接调用[self.cat.foods addObject:@"1"];
那么时不会出发KVO的, 因为KVO的本质是调用了派生类的set方法,而addObject没有调用Set方法,正确的使用方式应该是这样的:
[[self.cat mutableArrayValueForKey:@"foods"] addObject:@"1"];
注意点
在调用addObserver
方法后,KVO
并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash
KVO
的addObserver
和removeObserver
需要是成对的,如果重复remove
则会导致NSRangeException
类型的Crash
,如果忘记remove
则会在观察者释放后再次接收到KVO
回调时Crash
。
苹果官方推荐的方式是,在init
的时候进行addObserver
,在dealloc
时removeObserver
,这样可以保证add
和remove
是成对出现的,是一种比较理想的使用方式。
###