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

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

输出:


  • 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

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 *)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分析)