KVO的实现原理

KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO

KVONSNotificationCenter都是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:方法中。

实现过程

  1. 某个类(Cat)的属性在第一次被观察时,runtime会动态的创建一个Cat类的子类,名为NSKVONotifying_Cat,然后重写所有Cat类中被观察属性的Set方法,然后通过NSKVONotifying_Cat类中重写的Set方法实现属性改变的通知.
  2. 每个类对象都会有一个isa指针指向当前类,当我们第一次观察这个类时,原类Cat的isa指针就会指向其派生类NSKVONotifying_Cat,那么此时我们调用原类对象的set方法其实就是调用的派生类NSKVONotifying_Cat的set方法.
  3. 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey:和 didChangeValueForKey:在一个被观察属性发生改变之前 willChangeValueForKey:一定会被调用这就 会记录旧的值。而当改变发生后didChangeValueForKey:会被调用继而 observeValueForKey:ofObject:change:context:也会被调用。
  4. KVO的这套实现机制中苹果还偷偷重写了class方法让我们误认为还是使用的当前类从而达到隐藏生成的派生类

综上所述,其

kvo1

可变容器的监听

例如对Cat类的某个可变容器属性监听之后如果直接调用[self.cat.foods addObject:@"1"];那么时不会出发KVO的, 因为KVO的本质是调用了派生类的set方法,而addObject没有调用Set方法,正确的使用方式应该是这样的:

[[self.cat mutableArrayValueForKey:@"foods"] addObject:@"1"];

注意点

在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash

KVOaddObserverremoveObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash

苹果官方推荐的方式是,在init的时候进行addObserver,在deallocremoveObserver,这样可以保证addremove是成对出现的,是一种比较理想的使用方式。

###