iOS程序猿KVO与Notif通知iOS学习笔记

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

2021-06-28  本文已影响0人  Henry________

1、 KVO是什么?

2、 KVO的基本使用

基本使用分为4步:

2.1 注册观察者

[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];

2.2 被观察者发生变化

self.person.nickName = @"Henry";
[self.person setValue:@"Henry" forKey:@"nickName"];
[self setValue:@"Henry" forKeyPath:@"person.nickName"];

2.3 触发监听

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

输出:


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));

输出:


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]];

输出:


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")];
#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


触发断点之后发现:

  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));
}

输出:


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")];
}

输出:


总结

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保存到内存中,等待下次使用
上一篇 下一篇

猜你喜欢

热点阅读