美文网首页iOS程序猿KVO与Notif通知iOS学习笔记
OC底层探索20-KVO中的isa-swizzling分析

OC底层探索20-KVO中的isa-swizzling分析

作者: Henry________ | 来源:发表于2021-06-28 00:14 被阅读0次

    1、 KVO是什么?

    • KVO 全称Key Value Observing,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于 KVO 的实现机制,属性变化还有通过kvc进行修改的,一般继承自 NSObject 的对象都默认支持 KVO。
    • KVO 可以监听单个属性的变化,也可以监听集合对象的变化。集合对象需要通过 KVC 的 mutableArrayValueForKey:等方法获得代理对象(例如数组会创建:创建一个NSKeyValueSlowMutableArray中间对象),当代理对象的内部对象发生改变时,会回调 KVO 监听的方法。集合对象包含 NSArray 和 NSSet。

    2、 KVO的基本使用

    基本使用分为4步:

    2.1 注册观察者

    [self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
    
    • NSKeyValueObservingOptionNew:在触发函数返回新值;
    • NSKeyValueObservingOptionOld:在触发函数返回旧值;

    2.2 被观察者发生变化

    self.person.nickName = @"Henry";
    [self.person setValue:@"Henry" forKey:@"nickName"];
    [self setValue:@"Henry" forKeyPath:@"person.nickName"];
    
    • 这3种方式都可以,尤其是监听集合类型时需要格外注意,需要使用KVC。

    2.3 触发监听

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"%@",change);
    }
    

    输出:


    • kind:表示监听方式

    2.4 销毁

    - (void)dealloc{
        [self.person removeObserver:self forKeyPath:@"nickName"];
    }
    
    • 使用需要及时进行合法销毁;

    3、KVO原理

    3.1 isa-swizzling

    NSLog(@"添加KVO之前-%@-%p", object_getClass(self.person),object_getClass(self.person));
    
    [self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
    
    NSLog(@"添加KVO之后-%@-%p", object_getClass(self.person),object_getClass(self.person));
    

    输出:


    • 会发现在addObserver之后,类的Isa指向发生了变化
    3.1.1 NSKVONotifying_XXX 中间派生类

    猜测NSKVONotifying_LGPerson这个类是系统动态进行添加,所以需要分析它的进行关系。获取LGPerson的子类

    #pragma mark - 遍历类以及子类
    - (void)printClasses:(Class)cls{
        //获取所有类
        int count = objc_getClassList(NULL, 0);
        Class* classes = (Class*)malloc(sizeof(Class)*count);
        objc_getClassList(classes, count);
        for (int i = 0; i<count; i++) {
            if (cls == class_getSuperclass(classes[i])) {
                NSLog(@"classes = %@", classes[i]);
            }
        }
    }
    // 遍历类以及子类
    [slef printClasses:[LGPerson class]];
    [self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
    NSLog(@"绑定之后");
    // 遍历类以及子类
    [slef printClasses:[LGPerson class]];
    

    输出:


    • LGStudentLGPerson的一个子类;
    • LGPerson在绑定之后出现了一个新的子类NSKVONotifying_LGPerson;
    • kvo第一步之后会将对象self.person的isa动态指向了NSKVONotifying_LGPerson。这个类,这就是isa-swizzling
    3.1.2 NSKVONotifying_XXX 类中的有什么
        NSLog(@"绑定之后");
        NSLog(@"~~~LGPerson~~~方法~~~");
        // 遍历类的所有方法
        [self printClassAllMethod:[LGPerson class]];
        NSLog(@"~~~NSKVONotifying_LGPerson~~~方法~~~");
        [self printClassAllMethod:objc_getClass("NSKVONotifying_LGPerson")];   
        NSLog(@"~~~LGPerson~~~属性~~~");
        // 遍历类的所有属性
        [self printClassAllIvar:[LGPerson class]];
        NSLog(@"~~~NSKVONotifying_LGPerson~~~属性~~~");
        [self printClassAllIvar:objc_getClass("NSKVONotifying_LGPerson")];
    
    • 输出:


      • NSKVONotifying_LGPerson有四个方法:setNickName , class, dealloc, _isKVOA
      • NSKVONotifying_LGPerson中没有属性
    • 上方使用到的两个runtime方法:

    #pragma mark - 遍历方法
    - (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);
    }
    #pragma mark - 遍历属性-ivar
    - (void)printClassAllIvar:(Class)cls{
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(cls, &count);
        for (int i = 0; i<count; i++) {
            Ivar ivar = ivars[i];
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            NSLog(@"%@",ivarName);
        }
        free(ivars);
    }
    

    4、NSKVONotifying_XXX 中间派生类

    4.1 NSKVONotifying_XXX 伪代码

    // 这个核心方法作用是什么,在后面进行详解
    - (void)setNickName:(NSString *)name{
       ...
    }
    // 我觉是一种混淆,为了隐藏NSKVONotifying_LGPerson的存在不被开发者发现
    - (Class)class {
        return [LGPerson class];
    }
    //销毁
    - (void)dealloc {
        // 收尾工作
    }
    // 这个方法应该是当做KVO的一个标记
    - (BOOL)_isKVOA {
        return YES;
    }
    

    4.2 setNickName

    来到核心方法setNickName之后,由于NSKVONotifying_LGPerson类中的setNickName是系统生成的想要窥探一二就需要借助lldb

    • 添加一个观察点watchpoint set variable self->_person->_nickName

    触发断点之后发现:

    1. 调用了set方法中的NSKeyValueWillChange
    2. 调用了LGPerson原生类中的set方法;
    3. 调用了set方法中的NSKeyValueDidChange方法
    4. 最后由NSKeyValueDidChange调起了- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

    注: 还可以将这两个方法增加为System breakpoint,可以观察到更多信息,这里就不赘述了。

    4.3 delloc

    - (void)dealloc{
        NSLog(@"销毁之前-%@-%p", object_getClass(self.person),object_getClass(self.person));
        [self.person removeObserver:self forKeyPath:@"nickName"];
        NSLog(@"销毁之后-%@-%p", object_getClass(self.person),object_getClass(self.person));
    }
    

    输出:


    • 在销毁之后self.person的isa又被重新指向NSKVONotifying_xxx的父类;
    4.3.1 delloc之后NSKVONotifying_XXX中间派生类怎么样了?
    - (void)dealloc{
        [self.person removeObserver:self forKeyPath:@"nickName"];
        NSLog(@"销毁之后");
        // 类的关系
        [self printClasses:[LGPerson class]];
        // 中间类的方法
        [self printClassAllMethod:objc_getClass("NSKVONotifying_LGPerson")];
    }
    

    输出:


    • 即使LGPerson的isa已经不指向派生类,可派生类还是完整存在内存中.

    总结

    addObserver之后:

    1. 系统动态创建了中间派生类NSKVONotifying_xxx
      1.1 在派生类中重写了set,delloc方法,并创建新方法class,_isKVOA;
    2. 被观察的类(LGPerson)isa指向新建的中间派生类NSKVONotifying_xxx

    被观察的者发生变化:

    1. 调用了set方法中的NSKeyValueWillChange
    2. 调用了LGPerson原生类中的set方法;
    3. 调用了set方法中的NSKeyValueDidChange方法;
    4. 最后由NSKeyValueDidChange调起了回调方法将改变信息送出;

    被观察的者销毁时:

    1. 被观察的类的isa重新指向NSKVONotifying_xxx的父类
    2. NSKVONotifying_xxx保存到内存中,等待下次使用

    相关文章

      网友评论

        本文标题:OC底层探索20-KVO中的isa-swizzling分析

        本文链接:https://www.haomeiwen.com/subject/ghzxultx.html