Objective-C -- KVO原理

KVO简介


全称是Key-value observing,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。

KVO用法


  1. 注册观察者,实施监听;
[self.myKVO addObserver:self forKeyPath:@"num" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil]; ```
2. 在回调方法中处理属性发生的变化;

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
}

3. 移除观察者

[self.myKVO removeObserver:self forKeyPath:@"num"];


####KVO实现原理
***

当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为: NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。

######NSKVONotifying_A类剖析

在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听;

所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类,就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。 

因而在该对象上对 setter 的调用就会调用已重写的 setter(运行时重写的),从而激活键值通知机制。

######子类setter方法剖析

KVO的键值观察通知依赖于 NSObject 的两个方法:`willChangeValueForKey:`和 `didChangevlueForKey:`,需要在存取数值的前后分别调用这两个方法:
1. 被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;
2. 当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;
3. 之后, observeValueForKey:ofObject:change:context: 也会被调用。

KVO为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

-(void)setName:(NSString *)newName{
[self willChangeValueForKey:@"name"]; //KVO在调用存取方法之前总调用
[super setValue:newName forKey:@"name"]; //调用父类的存取方法
[self didChangeValueForKey:@"name"]; //KVO在调用存取方法之后总调用
}


####KVO触发条件
***
观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行KVO的回调方法,如

* 使用了setter方法赋值
* 使用了KVC赋值

如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发KVO机制的。所以使用KVO机制的前提是遵循 KVO 的属性设置方式来变更属性值。

推荐阅读更多精彩内容