一.KVO演示如何使用
假设我们创建一个Person类,去监听属性age的修改
@interface Person : NSObject
@property (**nonatomic, assign) int age;
@end
self.person = [[Person alloc] init];
_person.age = 10;
[_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
_person.age = 20;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"------%@ - %@",keyPath, change);
}
通过打印我们可以看到age新值和旧值,那KVO是如何实现的呢,其本质又是什么呢
二.探索本质
我们知道_person.age其实是调用了setAge方法,所以可以猜测监听一定是在set方法里面触发的,而setAge方法其实是存储在Person的类对象里,接下来我们打印一下监听前后Person的类对象及setAge方法的地址
NSLog(@"监听前类名------%s",object_getClassName(_person));
NSLog(@"监听前set方法地址------%p",[self.person methodForSelector: @selector(setAge:)]);
[_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"监听后类名------%s",object_getClassName(_person));
NSLog(@"监听后set方法地址------%p",[self.person methodForSelector: @selector(setAge:)]);
我们发现监听后_person类的类对象改为了NSKVONotifying_Person,接下来我们再通过断点调试打印p IMP(0x101b494f0)及p IMP(0x102b79cb3),我们可以看到如下打印信息
监听前:(test`-[Person setAge:] at Person.m:34)
监听后:(Foundation`_NSSetIntValueAndNotify)
我们可以得出结论,给person类添加监听后,系统创建出一个全新的类NSKVONotifying_Person,重写了setAge方法,实际调用Foundation框架的_NSSetIntValueAndNotify方法,因为Foundation框架闭源,我们只能通过逆向去查看具体实现,这里提供一下伪代码
- (void)setAge:(int)age
{
_NSSetIntValueAndNotify();
}
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
[observer observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
三.结论
当给对象Person添加监听方法addObserver:forKeyPath:options:context:时,
1.系统通过runtime新增一个NSKVONotifying_Person子类,继承自Person类
2.调用setAge:方法修改值时,会通过isa找到NSKVONotifying_Person类执行新的setAge:方法
3.调用NSKVONotifying_Person的_NSSetIntValueAndNotify()
1)调用willChangeValueForKey
2)[super setAge:age],执行Person类的setAge:进行值的修改
3)[self didChangeValueForKey:@"age"];
[observer observeValueForKeyPath:key ofObject:self change:nil context:nil]
让被监听对象执行observeValueForKeyPath方法
四.如何手动触发KVO
[_person willChangeValueForKey:@"age"];
[_person didChangeValueForKey:@"age"];