ios KVO及实现原理

概述
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 *)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"];

demo:https://github.com/destinyzhao/KOVAndKVCDemo

你可能感兴趣的:(ios KVO及实现原理)