这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
Hi ?
| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 |
|---|---|---|
| 类型 | 游戏 | 财务 |
| AppStore | Elic | Umemi |
一、 中间类

一般提到 KVO 底层,大家可能都知道是生成类一个 NSKVONotifying_XXX 的类。但是再往深了问可能就答不出来了。
1.1 中间类是动态生成的还是编译生成的?
在设置观察者之前我们通过调用 objc_getClass("NSKVONotifying_RYModel"),看能否获取到 NSKVONotifying_RYModel 类。

添加观察者之后

结论: 可见 NSKVONotifying_RYModel 确实是动态生成的,而不是编译完成后就已经存在的。
1.2 中间类 和 本类 的关系
先猜测 NSKVONotifying_RYModel 是否是 RYModel 的子类,来验证一下。
打印自己与所有子类
- (void)logSubClassChain:(Class)cls {
int count = objc_getClassList(NULL, 0);
NSMutableArray *tempArr = @[cls].mutableCopy;
Class *classes = (Class*)malloc(sizeof(Class) * count);
objc_getClassList(classes, count);
for (int i = 0; i < count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[tempArr addObject:classes[i]];
}
}
free(classes);
NSLog(@"Classes : %@", tempArr);
}
复制代码

发现添加观察者后输出的多了一个 NSKVONotifying_RYModel 证明了它确实是 RYModel 的子类
结论: KVO动态创建的类是本类的一个子类。
1.3 中间类的生命周期是怎样的?是否会移除?

结论: 移除观察者,观察者释放后并不会 NSKVONotifying_XXX 页依旧存在。
思考: 由于中间类一旦生成就一直存在,所以理论上也不宜过多使用 KVO。
1.4 中间类的方法列表和本类有什么区别?
添加打印方法的方法:
+ (void)logAllMethodOf:(Class)cls {
NSLog(@"%s %@", __func__, 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 = method_getImplementation(method);
NSLog(@"SEL: %@ IMP: %p", NSStringFromSelector(sel), imp);
}
free(methodList);
NSLog(@"\n");
}
复制代码

RYModel
- SEL: name IMP: 0x100003210
- SEL: .cxx_destruct IMP: 0x1000032a0
- SEL: setName: IMP: 0x100003170
- SEL: books IMP: 0x100003240
- SEL: setBooks: IMP: 0x100003260
NSKVONotifying_RYModel
- SEL: setBooks: IMP: 0x7fff212b9d2e
- SEL: setName: IMP: 0x7fff212b9d2e
- SEL: class IMP: 0x7fff2127cc06
- SEL: dealloc IMP: 0x7fff212bf9f5
- SEL: _isKVOA IMP: 0x7fff213e3e3a
1.5 中间类的方法是继承的还是重写的?
新建一个子类,重写父类的方法:
@implementation RYSubModel
- (void)setBooks:(NSMutableArray<NSString *> *)books {
[super setBooks:books];
}
@end
复制代码
输出:
// 子类
+[KVOTool logAllMethodOf:] RYSubModel
SEL: setBooks: IMP: 0x100003240
// 本类
添加观察者之前:RYModel
+[KVOTool logAllMethodOf:] RYModel
SEL: name IMP: 0x100003160
SEL: .cxx_destruct IMP: 0x1000031f0
SEL: setName: IMP: 0x1000030c0
SEL: books IMP: 0x100003190
SEL: setBooks: IMP: 0x1000031b0
// 中间类
添加观察者之后:NSKVONotifying_RYModel
+[KVOTool logAllMethodOf:] NSKVONotifying_RYModel
SEL: setBooks: IMP: 0x7fff212b9d2e
SEL: setName: IMP: 0x7fff212b9d2e
SEL: class IMP: 0x7fff2127cc06
SEL: dealloc IMP: 0x7fff212bf9f5
SEL: _isKVOA IMP: 0x7fff213e3e3a
复制代码
1.6 KVO 开关对方法列表的影响
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return YES;
}
复制代码
停止 name 自动发送通知后,中间类的方法列表中就没有类 setName。

二、 ISA 指向变换
上面我们知道被观察的对象类型变成类一个中间类。那么它是怎么变的呢?
2.1 改变
添加观察者之前

添加观察者之后

很明显实例的 ISA 的指向发生了变化。
2.2 恢复
开始移除观察者:

移除最后一个观察者:

观察者全部移除:

恢复了 RYModel
思考:全部Key移除了观察者才ISA恢复的本类,如果不完全移除呢?


剩了一个 Key 没有移除观察者

到最后实例的 ISA 指向还是 中间类。
三、 触发通知的过程

我们在 KVO 通知接收的地方下个断点,调用堆栈:
- tool.ryuk.name = @”Ryukie”;
- Foundation`_NSSetObjectValueAndNotify
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
- Foundation`NSKeyValueDidChange
- Foundation`NSKeyValueNotifyObserver
- -[KVOTool observeValueForKeyPath:ofObject:change:context:]
- Foundation`NSKeyValueNotifyObserver
- Foundation`NSKeyValueDidChange
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
- Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
- Foundation`_NSSetObjectValueAndNotify
总结
到此,我们知道了
- 使用 KVO 过程中动态生成了中间类
- 中间类和本类是继承关系,同时重写了本类的一些方法
- 自动监听开关对与中间类的结构是有影响的
- 被观察者 ISA 的指向会发生变化,完成移除观察Key后会恢复。
?欢迎点赞收藏关注♥️
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)