概述
KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。
KVO可以监听单个属性的变化,也可以监听集合对象的变化。通过KVC的mutableArrayValueForKey:等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO监听的方法。集合对象包含NSArray和NSSet。
基础使用
使用KVO分为三个步骤:
1.注册观察者,指定被观察对象的属性
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
2.在观察者中实现以下回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"%@",change);
}
3.当观察者不需要监听时
[person removeObserver:self forKeyPath:@"name"];
实现原理
KVO是通过isa-swizzling技术实现的。在运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指向中间类。并且将class方法重写,返回原类的Class。
测试代码
Person *person = [Person new];
person.name = @"old";
NSLog(@"before Class Name:%s",object_getClassName(person));
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"after Class Name:%s",object_getClassName(person));
控制台输出
2018-09-04 09:59:09.531941+0800 KOVAndKVCDemo[36344:19883033] before Class Name:Person
2018-09-04 09:59:09.532317+0800 KOVAndKVCDemo[36344:19883033] after Class Name:NSKVONotifying_Person
上面的原理结合代码原理可以这样理解:
当观察对象Person时,KVO机制动态创建一个新的名为: NSKVONotifying_Person的新类,该类继承自对象Person的本类,且KVO为NSKVONotifying_Person重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
怎么理解setter 方法会负责在调用原 setter 方法之前和之后,类似看如下代码
-(void)setName:(NSString *)name
{
[self willChangeValueForKey:@"name"]; //KVO在调用存取方法之前总调用
[super name forKey:@"name"]; //调用父类的存取方法
[self didChangeValueForKey:@"name"]; //KVO在调用存取方法之后总调用
}
NSKVONotifying_Person类剖析
在这个过程,被观察对象的 isa 指针从指向原来的Person类,被KVO机制修改为指向系统新创建的子类NSKVONotifying_Person类,来实现当前类属性值改变的监听;
子类setter方法剖析
KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法:
被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;之后, observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。
如何手动实现KVC?
[person willChangeValueForKey:@"name"];
person.name = @"Alex";
[person didChangeValueForKey:@"name"];
网友评论