前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OC底层探索20-KVO中的isa-swizzling分析OC底层探索20-KVO中的isa-swizzling分析

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

作者头像
用户8893176
发布2021-08-09 11:07:39
5850
发布2021-08-09 11:07:39
举报
文章被收录于专栏:小黑娃Henry

1、 KVO是什么?

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

2、 KVO的基本使用

基本使用分为4步:

2.1 注册观察者
代码语言:javascript
复制
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
  • NSKeyValueObservingOptionNew:在触发函数返回新值;
  • NSKeyValueObservingOptionOld:在触发函数返回旧值;
2.2 被观察者发生变化
代码语言:javascript
复制
self.person.nickName = @"Henry";
[self.person setValue:@"Henry" forKey:@"nickName"];
[self setValue:@"Henry" forKeyPath:@"person.nickName"];
  • 这3种方式都可以,尤其是监听集合类型时需要格外注意,需要使用KVC。
2.3 触发监听
代码语言:javascript
复制
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}

输出:

  • kind:表示监听方式
2.4 销毁
代码语言:javascript
复制
- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"nickName"];
}
  • 使用需要及时进行合法销毁;

3、KVO原理

3.1 isa-swizzling
代码语言:javascript
复制
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的子类

代码语言:javascript
复制
#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 类中的有什么
代码语言:javascript
复制
    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方法:
代码语言:javascript
复制
#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 伪代码
代码语言:javascript
复制
// 这个核心方法作用是什么,在后面进行详解
- (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
代码语言:javascript
复制
- (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中间派生类怎么样了?
代码语言:javascript
复制
- (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保存到内存中,等待下次使用
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/6/28 上,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、 KVO是什么?
  • 2、 KVO的基本使用
    • 2.1 注册观察者
      • 2.2 被观察者发生变化
        • 2.3 触发监听
          • 2.4 销毁
          • 3、KVO原理
            • 3.1 isa-swizzling
              • 3.1.1 NSKVONotifying_XXX 中间派生类
              • 3.1.2 NSKVONotifying_XXX 类中的有什么
          • 4、NSKVONotifying_XXX 中间派生类
            • 4.1 NSKVONotifying_XXX 伪代码
              • 4.2 setNickName
                • 4.3 delloc
                  • 4.3.1 delloc之后NSKVONotifying_XXX中间派生类怎么样了?
              • 总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档