iOS

iOS底层探索之KVO(二)—KVO原理分析

2021-08-05  本文已影响0人  俊而不逊

回顾

上一篇博客中,已经介绍了KVO的相关操作,那么接下来就去探索一下KVO的底层逻辑,KVO到底是如何实现的呢?

文章主题

键值观察是使用称为isa-swizzling的技术实现的。

1. isa-swizzling验证

在添加观察者处,打上断点,再控制台lldb调试看看。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.student = [[JPStudent alloc]init];
    [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}

addObserverstudentJPStudent变成了NSKVONotifying_JPStudent

我们知道,实例对象的关系实际上就是实例对象的isa指向了类对象。所以这里我们可以推断,self.student在调用addObserver方法后,已经从JPStudent类的实例对象,变成了NSKVONotifying_JPStudent
的实例对象。

2. NSKVONotifying_JPStudent子类验证

提醒objc_getClassruntimeapi,一定要导入头文件才可以正常使用,如图所示。

在调用addObserver方法前后分别打印,结果说明NSKVONotifying_JPStudent是系统动态生成添加的一个类。这两个类名字这么相似,有没有可能是JPStudent的子类呢?我们打印一下看看

验证NSKVONotifying_JPStudent是JPStudent的子类

从打印来看,发现了新大陆,NSKVONotifying_JPStudent 确实是继承自JPStudent的。那么这个中间类,有没有可能存在自己的子类呢?我们通过下面这段代码来看看

#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
     // 注册类的总数
     int count = objc_getClassList(NULL, 0);
     // 创建一个数组, 其中包含给定对象
     NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
     // 获取所有已注册的类
     Class* classes = (Class*)malloc(sizeof(Class)*count);
     objc_getClassList(classes, count);
     for (int i = 0; i<count; i++) {
          if (cls == class_getSuperclass(classes[i])) {
                [mArray addObject:classes[i]];
          }
     }
     free(classes);
     NSLog(@"classes = %@", mArray);
}
#pragma mark **- 遍历方法-ivar-property**
- (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);
}

从打印结果来看,系统重写了setNickNameclassdealloc这几个方法,并且添加了一个叫_isKVOA的方法,来区分是不是系统通过KVO自动生成的。

3. 观察者被移除isa的指向?

官方文档中说:调用addObserver方法会修改isa指向,那么现在我们移除观察者后系统会怎么做呢?我们在dealloc方法中移除观察者这里打上断点,然后继续观察self.studentisa指向。

观察者被移除isa的指向?

移除观察者之后,self.studentisa又指回了JPStudent类。并且生成的子类NSKVONotifying_JPStudent 还在,没有进行销毁。

原因是如果下次继续进行观察者添加,系统就不会再生成新的中间类,而是直接使用这个类了,防止资源的浪费。

4. class方法

在添加观察者之后,我们都知道会生成动态子类NSKVONotifying_JPStudent

那么调用class方法p self.student.class打印的是 NSKVONotifying_JPStudent 吗???

5. setter方法

既然重写了setter方法观察属性,那么如果有成员变量,是否也可以能观察呢?增加age成员变量,测试一下

@interface JPStudent : NSObject
{
    @public
    int age;
}
@property (nonatomic, copy) NSString *name;

@end 
setter方法测试

当对age进行赋值的时候,并没有触发监听的回调方法。那么就说明了只是对属性的setter方法进行的监听。

我们再看看在dealloc中观察者移除isa指回的时候,查看name的值

观察者移除后Name的值

那就说明在KVO生成的类中对name的修改影响到了原始类。

name下个内存断点调试看看

断点调试

发现调用了Foundation的一些方法,最后才是[JPStudent setName:]name赋值。

提示Foundation框架是不开源的。

_NSSetObjectValueAndNotify汇编调用主要如下:

_NSSetObjectValueAndNotify
"willChangeValueForKey:"
这里是调用setter方法赋值
"didChangeValueForKey:"
"_changeValueForKey:key:key:usingBlock:"

从堆栈信息和汇编可以知道,在_changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:中进行赋值后的回调,那么肯定得通知监听者

在这里插入图片描述

确认是在NSKeyValueNotifyObserver通知中进行的回调。

6.总结

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

上一篇 下一篇

猜你喜欢

热点阅读