一郎科技|iOS底层系列:KVO

前言最近打算重新梳理一遍iOS底层的知识 , 尽量把所有的底层知识点都搞懂搞透彻 , 碍于iOS不开源 , 有很多东西并不能很直观的去学习 , 所以可能有瑕疵 , 希望大家可以理解 , 并一起交流 , 笔者也尽可能做到尽善尽美吧 。
KVO概述KVO的底层是如何实现的呢?
对于这个问题 , 我想大家都可以简单的聊上这么几句 。
【一郎科技|iOS底层系列:KVO】对某个实例的某一个属性添加KVO监听后 , 系统会利用runtime的运行时特性 , 生成一个临时的类NSKVONotifying_xxx , 然后把该实例的isa指针指向NSKVONotifying_xxx , 监听哪个属性 , 就重写NSKVONotifying_xxx中此属性的set方法 , 然后在重写的set方法中实现监听和通知 。
简单的来说就是这样 , 但是这太笼统了 , 下面我们通过例子 , 一步一步的来分析 。
探究1. 为什么会想到可能是类发生了变化?Person * person1 = [[Person alloc] init];Person * person2 = [[Person alloc] init];[person1 addObserver:selfforKeyPath:@"age"options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOldcontext:nil];person1.age = 10;person2.age = 20;这是一个最基本的KVO使用 , 在回调中只有person1的值改变被监听了 , 但是我们在赋值的时候都是调用了age的set方法 , 如果我们在Person类中实现setAge:的方法并debug , 这两次赋值都会走setAge方法 , 问题不是出在setAge这里 , 所以我们推测可能是类发声了某些变化 。 (此处应该有runtime的知识基础 , runtime有能力对类做一些动态的改变) 。
所以我们可以获取一下这两个实例的类型
// 输出 person1:NSKVONotifying_PersonNSLog(@"person1:%@",object_getClass(person1));// 输出 person2:PersonNSLog(@"person2:%@",object_getClass(person2));到这里我们就可以确定确实是生成了一个中间类 。 并且让person1的isa指针指向了这个类(object_getClassName方法就是返回isa的指向) 。
注:此处为什么要用runtime的api , 因为runtime的api调用后的结果更加接近本质2. NSKVONotifying_Person类中做了什么处理?首先我们先看一下这面这个图 , 其实这就是添加了KVO之后类的类型结构
一郎科技|iOS底层系列:KVO关于NSKVONotifying_Person类实现的方法 , 我们是怎么样得到的呢 , 这里我们可以借助runtime的api窥探一下 。
[self printMethodList:object_getClass(person1)];// 下面是方法实现- (void)printMethodList:(Class)cls {unsigned int count;Method * methodList = class_copyMethodList(cls,for (unsigned int i = 0; i < count; i++) {Method method = methodList[i];NSLog(@"method(%d) : %@", i, NSStringFromSelector(method_getName(method)));}free(methodList);}输出结果
method(0) : setAge:method(1) : classmethod(2) : deallocmethod(3) : _isKVOA到这一步 , 我们可以先做一下小总结:
person2的isa指针指向Person类 , 所以在setAge的时候 , 就直接调用了Person中实现的setAge:方法 , 正常的赋值操作 , 没有触发KVO 。 但是person1的isa动态改变 , 指向了NSKVONotifying_Person , 同时NSKVONotifying_Person中又重新实现了setAge:方法 , 所以在给person1的age赋值时 , 首先调用的是NSKVONotifying_Person中的setAge:方法 , 但是我们在之前的debug中发现 , Person中的setAge:方法也会调用 , 其实这很容易理解 , 这应该是在NSKVONotifying_Person的setAge:实现中又调用了Person的setAge , 毕竟NSKVONotifying_Person的isa指向Person(请自行验证) 。


推荐阅读