KVO 是通过isa混写技术实现的,在运行时根据原始类创建一个中间类,这个中间类是这个原类的子类,并动态的修改当前对象的isa,指向中间类,并将class方法重写,返回原类的class。
所以苹果建议在开发中不应该依赖isa指针,而是通过class实例方法来获取对象类型。
一.举例实现
.这里新建一个Peson,继承NSObject,person有一个age属性。对这个对象的 age属性进行KVO
问题:
通过addObserver添加 注册观察者, self ->观察者 我, 关联一个观察者, 给Person添加一个观察者, p是否强引用self ?
答:KVO不能对观察者强引用,注意观察者生命周期,但p会调用observerValueForPath 方法
二.运行后查看变化
通过object_getClass 获取实例对象 的isa指向的类对象,
然后通过po 命令打印 发现kVO之前还是原来的Person对象,KVO之后就变成了NSKVONotifying_Person这个对象了,通过查看源码发现这个对象的结构有 isa,supperClass,_isKVOA,这些属性。
三.查看添加监听前后的方法的具体实现
设置监听之前:调用的是 MLPerson 里边的 setAge:方法
设置监听之后:调用的是新的子类里边的 setAge:方法,只不过 里边调用了 Foundation框架 里边的 _NSSetIntValueAndNofify方法
四、窥探 _NSSetIntValueAndNofify 方法实现
_NSSetIntValueAndNotify的内部实现
1.先调用了 willChangeValueForKey:方法
2.接着调用 父类的 setAge:方法(也就是MLPerson里边的setAge方法,使age的值真正发生变化)
3.发生变化之后 会接着调用 didChangeValueForKey: 方法
在此方法内部,通知监听器 某个对象的 属性值 发生了变化 接着触发 KVO的监听方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
五、如果修改了 成员变量会不会触发KVO?
不会。因为没有调用 setter方法
以上触发方式是自动触发,默认是自动触发的。
六、如何手动触发KVO
还有一个手动触发,(automatica 模式调整)主要是应用于开发中的一些场景。
所以手动触发,在willChangevalueforkey 和 didchangevalueforkey 中间 改变值,手动触发不管有没有属性的改变 只要调用 这两个方法就会被触发。
另外 KVO被触发后,一定是触发了这个对象的 set方法,或者KVC赋值,
七、iOS 默认不支持数组的KVO
但是iOS 默认不支持数组的KVO,kVO只对set方法会触发,但是对add不会触发。
对于一个普通可变数组,我们对这个数组KVO,直接监听当前的count或者lastObject 都会导致崩溃。
方法一:我们要把数组包装一层,新建一个类,这个类中申明一个可变数组,然后对这个类的数组属性KVO
_arrayModel = [ArrayModel new];
[_arrayModel addObserver:self forKeyPath:@"dataArray" options:NSKeyValueObservingOptionNew context:nil];
特别注意不能用 array add remove 要用
[[_arrayModel mutableArrayValueForKeyPath:@"dataArray"] addObject:XXX];
方法二:手动调用didChandgeValueForKey方法、
//要监听的数组,不可变数组也一样
@property (nonatomic, copy) NSMutableArray *array;
//另外在想要发出array变化的地方调用
[self willChangeValueForKey:@"array"];
//这边模拟元素内容变化,实际上调用了willChangeValueForKey
和didChangeValueForKey
方法,不管数组内容有没有变化都会告知监听者
[self..array addObject:@"piaojin"];
[self didChangeValueForKey:@"array"];
KVO 属性依赖 (属性中有个 dog 类对象,这个dog有个age 属性)
观察 keyPath dog.age 也是可以的
就要用到一个方法 keyPathForValueAffectingValueForKey
最后
而在这个整个过程中重写class方法目的就是不暴露出这个中间类。
因为当对象调用class方法时,会在自己的方法缓存列表,方法列表,父类缓存,父类方法
列表一直向上查找,因为class方法是NSObject中的方法,如果没有重写最终可能会返回这个中间类。
NSKVONotifying_Person,会给开发人带来疑惑。