上一篇介绍了KVO的基础知识点
这一篇来了解其实现原理
先说结论,然后围绕这些问题来进行验证
原理概述总结 isa-swizzling.
比如注册观察Person *person
1、注册观察之后,动态创建一个NSKVONotifying_Xxx
的类,该类继承其原来的类(NSKVONotifying_Person : Person)。
2、将观察对象的isa指向新的类。
3、在新类中重写 setter
、class
、dealloc
方法 ,新增 _isKVOA
方法
4、移除观察的时候,重新将观察对象的isa指回原来的类
5、原来的类保留
官方文档 对细节的解释:
键值观察实施细节
使用称为isa-swizzling的技术可以实现自动键值观察。
该
isa
指针,顾名思义,指向对象的类,它保持一个调度表。该分派表实质上包含指向该类实现的方法的指针以及其他数据。当为对象的属性注册观察者时,将修改观察对象的isa指针,指向中间类而不是真实类。结果,isa指针的值不一定反映实例的实际类。
您永远不要依靠
isa
指针来确定类成员。相反,您应该使用该[class]
方法确定对象实例的类。
注册观察之后,系统干了什么
从文档上看,当注册观察的时候,为观察对象创建了一个中间类,并将对象的isa指向该类,而不在指向原来的类。
通过代码来打印看看是什么样的
_person = [Person new];
// NSLog(@"_person class = %s",object_getClassName(_person));
NSLog(@"_person class = %@",object_getClass(_person));
[_person addObserver:self forKeyPath:@"personName" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:viewkeyPaht];
// NSLog(@"_person class = %s",object_getClassName(_person));
NSLog(@"_person class = %@",object_getClass(_person));
//打印结果
_person class = Person
_person class = NSKVONotifying_Person
从代码上来看,person的isa指向已经发生了变化。动态添加了一个NSKVONotifying_Person
类。 还有一个问题:动态创建的NSKVONotifying_Person
类与原来的Person
类 是什么关系?
因为NSKVONotifying_Person类还需要用到Person
的属性等信息, 所以大胆猜测以下, 应该是继承关系。用打印的方式来验证:
Class newCls = object_getClass(_person);
if ([newCls superclass] == [Person class]) {
NSLog(@"动态添加的类 继承于 Person");
}else if ([[Person class] superclass] == newCls) {
NSLog(@"Person 继承于 动态添加的类 ");
}else {
NSLog(@"没有直接的继承关系");
}
// 打印结果: 动态添加的类 继承于 Person
//或者使用这套API 打印 指定的类及其子类
- (void)printClass:(Class)cls {
int count = objc_getClassList(NULL, 0); //所有注册的类总量
NSMutableArray *arr = [NSMutableArray arrayWithObject:cls]; //创建一个数组 包含给定的对象
Class *classes = (Class *)malloc(sizeof(Class) * count); // 分配内存空间
objc_getClassList(classes, count); //对 classes 进行填充
for (int i = 0; i < count; i ++) {
if (cls == class_getSuperclass(classes[i])) {
[arr addObject:classes[i]];
}
}
free(classes);
NSLog(@"类及其子类 %@",arr);
}
//打印结果
类及其子类 (
Person,
"NSKVONotifying_Person"
)
动态添加的类结构
使用以下代码打印类信息
//打印成员变量/属性
void testObjc_copyIvar_copyProperies(Class pClass){
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Ivar const ivar = ivars[i];
//获取实例变量名
const char*cName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:cName];
NSLog(@"class_copyIvarList:%@",ivarName);
}
free(ivars);
unsigned int pCount = 0;
objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
for (unsigned int i=0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//获取属性值
NSLog(@"class_copyProperiesList:%@",propertyName);
}
free(properties);
}
// 打印方法列表
// 如果传入类 就是打印的实例方法/静态方法
// 如果传入元类 就打印的是类方法
void testObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method name: %@", key);
}
free(methods);
}
对_person对象注册观察之后,打印Person类信息 和动态创建的NSKVONotifying_Person类信息
testObjc_copyIvar_copyProperies(NSClassFromString(@"Person"));
testObjc_copyMethodList(NSClassFromString(@"Person"));
testObjc_copyIvar_copyProperies(NSClassFromString(@"NSKVONotifying_Person"));
testObjc_copyMethodList(NSClassFromString(@"NSKVONotifying_Person"));
// person 信息
Ivar:_personName
property:personName
Method name: .cxx_destruct
Method name: personName
Method name: setPersonName:
// NSKVONotifying_Person 信息
Method name: setPersonName:
Method name: class
Method name: dealloc
Method name: _isKVOA
动态添加了类,干了什么?
- 没有任何属性和成员变量
- 动态添加的类,重写了setter、class、dealloc方法,并新增了一个 _isKVOA 方法
为什么要做这些事?
1、重写setter方法:
---- 重写的前提是这个setter方法存在。如果添加的key是分类的属性,并且没有实现setter方法、或者添加的keyPath 不是一个正确的属性,setter不会被重写。
我们在前面有提到过,注册观察之后,之所以会触发KVO,是因为系统在合适的地方,自动添加了willChange和didChange方法,那么系统究竟是在什么时机添加的方法呢? 就是在这里:添加类之后,重写其setter方法
2、重写class方法
在文章开头的 《键值观察实施细节》 部分的最后,有一句:不要用isa去找类成员,而是应该用class方法去找,为了达到这个目的,因为_person的isa已经发生了变化,其指向了NSKVONotifying_Person
,如果不重写class方法的话,用 [obj class] 得到的结果就是创建的新类,而不是Person,为了保证 调用class方法 还是得到原来的类Person,所以这里进行了重写
3、 重写dealloc方法
因为在移除观察之后,要使观察对象的isa重新指向原来的类,就是在这里进行操作
4、添加_isKVOA方法
重写这个方法 是为了标记这个类是在注册观察之后动态创建的类。
移除观察之后干了什么
到了这里,已经知道了 注册观察后,创建了一个类,并改变了对象的isa,那么在移除观察之后,
1、isa是否指回原来的类?
2、动态创建的类是否还存在?
还是通过打印来验证
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"移除前,person的isa = %@",object_getClass(_person));
[_person removeObserver:self forKeyPath:@"personName"];
NSLog(@"移除后,person的isa = %@",object_getClass(_person));
[self printClass:NSClassFromString(@"Person")];
}
// 打印结果
移除前,person的isa = NSKVONotifying_Person
移除后,person的isa = Person
类及其子类 (
Person,
"NSKVONotifying_Person"
)
移除观察前后的变化
1、 移除观察前,isa指向动态创建的类
2、移除观察后,isa指向了原来的类
3、移除观察后,动态创建的类还是存在于内存中。
- 为什么移除观察后,动态创建的类不移除,还要保存在内存中?
效率,一切为了效率。一般来说,注册观察之后创建了类 之后大概率还会对其注册观察,而创建/移除类是比较耗时和耗性能的。所以综合考虑是浪费一点内存比较好。