iOS底层--KVO(二)-原理

上一篇介绍了KVO的基础知识点
这一篇来了解其实现原理

先说结论,然后围绕这些问题来进行验证

原理概述总结 isa-swizzling.

比如注册观察Person *person
1、注册观察之后,动态创建一个NSKVONotifying_Xxx的类,该类继承其原来的类(NSKVONotifying_Person : Person)。
2、将观察对象的isa指向新的类。
3、在新类中重写 setterclassdealloc方法 ,新增 _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 *)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、移除观察后,动态创建的类还是存在于内存中。

移除观察后的isa
  • 为什么移除观察后,动态创建的类不移除,还要保存在内存中?
    效率,一切为了效率。一般来说,注册观察之后创建了类 之后大概率还会对其注册观察,而创建/移除类是比较耗时和耗性能的。所以综合考虑是浪费一点内存比较好。

你可能感兴趣的:(iOS底层--KVO(二)-原理)