kVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象的属性值的改变
基础使用和概念不赘述,我们直入正题。
1、为什么可以监听,他是怎么监听的?
以下列类为例,简写代码如下
self.person = [[HXHPerson alloc] init];
self.person.age = 10;
self.person2 = [[HXHPerson2 alloc] init];
self.person2.age = 100;
//添加kvo监听
[self.person addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
触发值的变化
self.person.age = 20;
self.person2.age = 300;
接受值的变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"object: %@\nchange : %@", object, change);
}
}
- (void)setAge:(int)age {
NSLog(@"谁修改了age: %p", self);
}
以上例子,同一个类,不同实例,监听同一个属性 LLDB打印isa
可以看到,添加过observer之后的实例对象的isa指向已经不是类对象 HXHPerson, 变成了 NSKVONotifying_HXHPerson
这就是KVO的机制
NSKVONotifying_HXHPerson是 runtime动态创建的,HXHPerson 的子类.
内部构造的不同:
重写了相应属性的set方法,以上边的age为例,它是去调用了Foundation框架的 _NSSetIntValueAndNotify;
Foundation的源码看不到,但是断点可以看到
可以手动创建一个 NSKVONotifying_HXHPerson 类,这个时候会发现KVO失效了,这也是从侧面证明了上边的结论。
接下来做另一个验证
NSLog(@"person添加监听前 : %p %p",
[self.person methodForSelector:@selector(setAge:)],
[self.person methodForSelector:@selector(setAge:)]);
//添加kvo监听
[self.person addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
NSLog(@"person添加监听后 : %p %p",
[self.person methodForSelector:@selector(setAge:)],
[self.person methodForSelector:@selector(setAge:)]);
打印输出的方法名前后的变化
2、_NSSetXxxxValueAndNotify的内部实现
伪代码如下
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
验证的话可以在willChange和didChage中分别log,不是难事,不再赘述
3、NSKVONotifying_xxx子类的实现
除了重写了setAge之外还重写了class,dealloc,_isKVO三个方法。 代码验证
- (void)printMethodNamesOfClass:(Class)cls {
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"\n%@ %@", cls, methodNames);
}
在使用的地方调用
[self printMethodNamesOfClass:object_getClass(self.person)];
[self printMethodNamesOfClass:object_getClass(self.person2)];
log截图如下
3.1、class方法
class方法直接返回原来的类对象,object_getClass()返回真实的当前类对象
为什么要重写这个方法?
不希望暴露这个类,不暴露内部实现。那有没有别的原因呢,目前没想到。
4、总结
本质就是runtime动态生成NSKVONotifying_xxx子类,并让instance对象的isa指向这个全信的子类,当修改instance对象的属性时会调用_Foundation的_NSSetXXXValueForKey函数,此函数内部会依次调用:
willChangeValueForKey;
父类原来的setter;
didChangeValueForKey;
内部触发Observer的监听监听方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;