回顾
在上一篇博客中,已经介绍了KVO
的相关操作,那么接下来就去探索一下KVO
的底层逻辑,KVO
到底是如何实现的呢?
-
在官方文档中有如下图中的说明
isa-swizzling
键值观察是使用称为
isa-swizzling
的技术实现的。
-
该
isa
指针,顾名思义,指向对象的类,它保持一个调度表。该调度表主要包含指向类实现的方法的指针,以及其他数据。 -
当观察者为对象的属性注册时,被观察对象的
isa
指针被修改,指向中间类
而不是真正的类
。因此,isa
指针的值不一定反映实例的实际类。 -
你不应该依赖
isa
指针来确定类的成员。相反,应该使用该class
方法来确定实例对象的类。
1. isa-swizzling验证
在添加观察者处,打上断点,再控制台lldb
调试看看。
- (void)viewDidLoad {
[super viewDidLoad];
self.student = [[JPStudent alloc]init];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
-
控制台打印如下
lldb调试
在
addObserver
后student
从JPStudent
变成了NSKVONotifying_JPStudent
我们知道,实例对象
和类
的关系实际上就是实例对象的isa
指向了类对象
。所以这里我们可以推断,self.student
在调用addObserver
方法后,已经从JPStudent
类的实例对象,变成了NSKVONotifying_JPStudent
的实例对象。
2. NSKVONotifying_JPStudent子类验证
- 那么这个
NSKVONotifying_JPStudent
是什么东西呢?是一开始就直接存在,还是和JPStudent
类之间有什么关系呢?那么看看NSKVONotifying_JPStudent
是不是一开始就存在的,再次运行代码,断点还是断在添加观察者者处,打印一下
NSKVONotifying_JPStudent生产测试
提醒
:objc_getClass
是runtime
的api
,一定要导入头文件才可以正常使用,如图所示。
在调用addObserver
方法前后分别打印,结果说明NSKVONotifying_JPStudent
是系统动态生成添加的一个类。这两个类名字这么相似,有没有可能是JPStudent
的子类呢?我们打印一下看看
从打印来看,发现了新大陆,
NSKVONotifying_JPStudent
确实是继承自JPStudent
的。那么这个中间类,有没有可能存在自己的子类呢?我们通过下面这段代码来看看
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
// 注册类的总数
int count = objc_getClassList(NULL, 0);
// 创建一个数组, 其中包含给定对象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 获取所有已注册的类
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[i]];
}
}
free(classes);
NSLog(@"classes = %@", mArray);
}
- 打印结果如下
遍历类以及子类
从打印来看,可以验证NSKVONotifying_JPStudent
是JPStudent
的子类。
那么NSKVONotifying_JPStudent
这个类里面都有些什么内容呢?类里面一般也就是存储了成员变量
、方法
、协议
等信息,那么通过下面这段代码来看看它里面都有什么。
#pragma mark **- 遍历方法-ivar-property**
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
-
打印如下
遍历方法-ivar-property
从打印结果来看,系统重写了
setNickName
、class
、dealloc
这几个方法,并且添加了一个叫_isKVOA
的方法,来区分是不是系统通过KVO
自动生成的。
3. 观察者被移除isa的指向?
官方文档中说:调用addObserver
方法会修改isa
指向,那么现在我们移除观察者后系统会怎么做呢?我们在dealloc
方法中移除观察者这里打上断点,然后继续观察self.student
的isa
指向。
移除观察者之后,
self.student
的isa
又指回了JPStudent
类。并且生成的子类NSKVONotifying_JPStudent
还在,没有进行销毁。原因是如果下次继续进行观察者添加,系统就不会再生成新的中间类,而是直接使用这个类了,防止资源的浪费。
4. class方法
在添加观察者之后,我们都知道会生成动态子类NSKVONotifying_JPStudent
,
那么调用
class
方法p self.student.class
打印的是NSKVONotifying_JPStudent
吗???
- 断点在添加观察者之后,我们控制台验证一下
self.student.class
从打印结果看,输出的还是JPStudent
,虽然self.student
的isa
已经指向NSKVONotifying_JPStudent
了,但是由于NSKVONotifying_JPStudent
重写了class
方法,最后打印输出的还是JPStudent
,苹果这么做的目的是为了隐藏系统在背后做的一系列操作,让开发者更少的关注底层逻辑,只关注上层的代码实现就可以。
5. setter方法
既然重写了setter
方法观察属性,那么如果有成员变量,是否也可以能观察呢?增加age
成员变量,测试一下
@interface JPStudent : NSObject
{
@public
int age;
}
@property (nonatomic, copy) NSString *name;
@end
setter方法测试
当对
age
进行赋值的时候,并没有触发监听的回调方法。那么就说明了只是对属性的setter
方法进行的监听。
我们再看看在dealloc
中观察者移除isa
指回的时候,查看name
的值
那就说明在
KVO
生成的类中对name
的修改影响到了原始类。
对name
下个内存断点调试看看
-
bt 打印堆栈信息
bt 打印堆栈信息
发现调用了
Foundation
的一些方法,最后才是[JPStudent setName:]
给name
赋值。
提示
:Foundation
框架是不开源的。
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
- Foundation`_NSSetObjectValueAndNotify
_NSSetObjectValueAndNotify
_NSSetObjectValueAndNotify
汇编调用主要如下:
"willChangeValueForKey:"
这里是调用setter方法赋值
"didChangeValueForKey:"
"_changeValueForKey:key:key:usingBlock:"
从堆栈信息和汇编可以知道,在
_changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:
中进行赋值后的回调,那么肯定得通知监听者
- 在
observeValueForKeyPath
的回调中打个断点:
确认是在
NSKeyValueNotifyObserver
通知中进行的回调。
6.总结
-
KVO
添加观察者addObserver
动态生成子类NSKVONotifying_XXX
。 - 重写
class
方法,返回父类class
信息。父类isa
指向子类。
给动态子类添加setter
方法(所有要观察的属性)。
消息转发给父类。 -
setter
会调用父类原来的方法进行赋值,完成后进行回调通知。 - 移除
observer
的时候isa
指回父类,动态生成的子类并不会销毁.
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
网友评论