通过输出结果可以发现person1的setAge已经被重写了,改成了调用Foundation框架中C语言写的 _NSSetIntValueAndNotify 方法,
还有一点,监听的属性值类型不同,调用的方法也不同,如果是NSString的,就会调用 _NSSetObjectValueAndNotify 方法,会有几种类型
大家都知道苹果的代码是不开源的,所以我们也不知道 _NSSetIntValueAndNotify 这个方法里面到底调用了些什么,那我们可以试着通过其它的方式去猜一下里面是怎么调用的。
KVO底层的调用顺序
我们先对我们自定义的类下手,重写下类里面的几个方法:
类实现:
#import "XGPerson.h"
@implementation XGPerson
- (void)setAge:(int)age{ _age = age; NSLog(@"XGPerson setAge"); } - (void)willChangeva lueForKey:(NSString *)key{ [super willChangeva lueForKey:key]; NSLog(@"willChangeva lueForKey"); } - (void)didChangeva lueForKey:(NSString *)key{ NSLog(@"didChangeva lueForKey - begin"); [super didChangeva lueForKey:key]; NSLog(@"didChangeva lueForKey - end"); }
重写上面3个方法来监听我们的值到底是怎么被改的,KVO的通知回调又是什么时候调用的
我们先设置KVO的监听回调
// KVO监听回调
- (void)observeva lueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值改变了 - %@", object, keyPath, change[@"new"]);
}
我们直接修改person1的age值,触发一下KVO,输出如下:
2018-11-02 15:38:24.788395+0800 KVO原理[4298:186471] willChangeva lueForKey
2018-11-02 15:38:24.788573+0800 KVO原理[4298:186471] XGPerson setAge
2018-11-02 15:38:24.788696+0800 KVO原理[4298:186471] didChangeva lueForKey - begin
2018-11-02 15:38:24.788893+0800 KVO原理[4298:186471] 监听到<XGPerson: 0x60400022f420>的age属性值改变了 - 2
2018-11-02 15:38:24.789014+0800 KVO原理[4298:186471] didChangeva lueForKey - end
从结果中可以看出KVO是在哪个时候触发回调的,就是在 didChangeva lueForKey 这个方法里面触发的
NSKVONotifying_XGPerson子类的研究
接下来我们再来研究下之前上面说的那个 NSKVONotifying_XGPerson 子类,可能大家会很好奇这里面到底有些什么东西,下面我们就使用runtime将这个子类的所有方法都打印出来
我们先写一个方法用来打印一个类对象的所有方法,代码如下:
// 获取一个对象的所有方法
- (void)getMehtodsOfClass:(Class)cls{
unsigned int count;
Method* methods = class_copyMethodList(cls, &count);
NSMutableString* methodList = [[NSMutableString alloc]init];
for (int i=0; i < count; i++) {
Method method = methods[i];
NSString* methodName = NSStringFromSelector(method_getName(method));
[methodList appendString:[NSString stringWithFormat:@"| %@",methodName]];
}
NSLog(@"%@对象-所有方法:%@",cls,methodList);
// C语言的函数是需要手动释放内存的喔
free(methods);
}
下面使用这个方法打印下person1的所有方法,顺便我们再对比下 object_getClass 和 class
// 一定要使用 object_getClass去获取类对象,不然获取到的不是真正的那个子类,而是XGPperson这个类
[self getMehtodsOfClass:object_getClass(self.person1)];
// 使用 class属性获取的类对象
[self getMehtodsOfClass:[self.person1 class]];
输出:
2018-11-02 15:45:07.918209+0800 KVO原理[4369:190437] NSKVONotifying_XGPerson对象-所有方法:| setAge:| class| dealloc| _isKVOA
2018-11-02 15:45:07.918371+0800 KVO原理[4369:190437] XGPerson对象-所有方法:| .cxx_destruct| name| willChangeva lueForKey:| didChangeva lueForKey:| setName:| setAge:| age
通过结果可以看出,这个子类里面就是重写了3个父类方法,还有一个私有的方法,我们XGPerson这个类还有一个name属性,这里为什么没有setName呢?因为我们没有给 name 属性添加KVO,所以就不会重写它,这里面确实有那个 class 方法,确实被重写了,所以当我们使用 [self.person1 class] 的方式的时候它内部怎么返回的就清楚了。
NSKVONotifying_XGPerson 伪代码实现
通过上面的研究,我们大概也能清楚NSKVONotifying_XGPerson这个子类里面是如何实现的了,大概的代码如下:
头文件:
@interface NSKVONotifying_XGPerson : XGPerson
@end
实现: