iOS-KVO本质

116 阅读2分钟

一.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:)]);

截屏2022-03-25 上午11.21.23.png

我们发现监听后_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"];