KVO的底层实现原理
2017-02-14 本文已影响79人
6ffd6634d577
什么是KVO ?
KVO即Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。
简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
KVO的使用比较简单,基本上都是三步:
- 注册观察者
addObserver:forKeyPath:options:context:
- 观察者中实现
observeValueForKeyPath:ofObject:change:context: - 移除观察者
removeObserver:forKeyPath:
接下来我们通过一个小Demo来探索KVO的底层实现:
1.新建工程后创建一个Person类
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@end
-----------------------------------------
#import "Person.h"
@implementation Person
@end
控制器里的代码如下:
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property(nonatomic,strong)Person *p;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person * p =[[Person alloc] init];
self.p = p;
p.name = @"张三";
p.height = 170;
//添加KVO 监控 p 当中 属性height 的改变
[p addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
p.height = 180;
}
//监控属性变化触发的方法..
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"height"]) {
NSLog(@"%@",change);
}
}
-(void)dealloc
{
[self.p removeObserver:self forKeyPath:@"height"];
}
第一次在添加监听者的时候打断点p对象的isa指针如下:

再往下走一步的时候p的isa指针如下:

再往下走的时候就会出发KVO的监听方法

接下来我们用setValue:forKey(KVC)方法给p的属性赋值:
// p.height = 180;
[p setValue:@(180) forKey:@"height"];
控制台打印结果如下:

接下来我们测试一下直接给p的属性赋值,不通过set方法:
在Person类中添加一个方法:
-(void)changeHeight
{
_height = 200;
}
在控制器类中:
// p.height = 180;
// [p setValue:@(180) forKey:@"height"];
[p changeHeight];
控制台没有打印任何信息,说明没有触发KVO的监听方法:

总结:
可见,在addObserver:forKeyPath:options:context:之后,对象p的isa变为了NSKVONotifying_Person。
所以,根据上面的isa介绍,响应setAge的方法,实际上是对象p的isa即NSKVONotifying_Person类的setAge方法,并不是原Person类的setAge方法。
可以大胆想象,NSKVONotifying_Person类是Person类的子类,在NSKVONotifying_Person类内部,重写了setAge方法,并且在setAge方法里让监听器调用了observeValueForKeyPath:ofObject:change:context:方法。
那么,NSKVONotifying_Person是怎么产生的呢?简单的说,是由runtime产生的。