iOS学习

iOS-底层原理21-KVO(下)

2021-01-19  本文已影响0人  一亩三分甜

iOS-底层原理21-KVO(下)

《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理20-KVO(上)》介绍到自定义KVO中观察到属性的值发生变化后,怎么通知到自定义的方法中

1.自定义KVO,属性值变化后通知到自定义方法中

来到lg_setter方法中之后,改变父类中nickName的值,进行消息发送,往父类发送消息setNickName:,重定义objc_msgSendSuper,传入三个参数objc_super父类结构体指针,_cmd(setNickName:)方法,newValue新改变的值,进入自定义子类LGKVONotifying_LGPerson的父类LGPerson的setNickName:方法中

    // 4: 消息转发 : 转发给父类
    // 改变父类的值 --- 可以强制类型转换
    void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    // void /* struct objc_super *super, SEL op, ... */
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
image.png
image.png
Class lg_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
image.png

下面这种写法会死循环:class_getSuperclass([self class])中[self class]还会调用lg_class方法


image.png
image.png

此时self.person.class的指向才和系统观察者一模一样了


image.png
此时执行程序发现无法找到setNickName:方法,为什么呢?分析原因,取了两次父类.super_class = class_getSuperclass([self class])中[self class]调用lg_class方法,class_getSuperclass(object_getClass(self))又取一次父类,即LGKVONotifying_LGPerson的父类的父类,并不存在setNickName:所以报unrecognized selector
image.png
image.png
正确的写法为.super_class = [self class]LGKVONotifying_LGPerson的父类LGPerson中查找setNickName:方法
image.png
    void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    // void /* struct objc_super *super, SEL op, ... */
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = [self class],
    };
    //objc_msgSendSuper(&superStruct,_cmd,newValue)
    lg_msgSendSuper(&superStruct,_cmd,newValue);
image.png

2.若要观察多个属性呢,以上会保存多次观察者VC,这里需要优化。

3.移除观察者

image.png

以上自定义的KVO是通过传值的方法,观察到值的变化后,在不同的方法中进行通知,有没有函数式的方式y=f(x)的方式,和添加观察者耦合再一起呢?

4.自定义函数式KVO

image.png image.png image.png image.png image.png

5.KVO自动销毁机制

在VC的dealloc方法中调用移除观察者方法[self.person lg_removeObserver:self forKeyPath:@"nickName"],怎么对观察者进行自动移除呢?在什么时候自动移除呢?VC进行销毁的时候,会调用dealloc方法,VC销毁,则持有的属性LGPerson必定销毁了,在VC的dealloc()方法中会给LGPerson发送release消息[self.person release],我们可以监听self.person是什么时候销毁的,即观察LGPerson是什么时候析构(走入-(void)dealloc方法)的,要想将自定义观察者自动移除,我们想到一种办法是监听LGPerson的析构函数-(void)dealloc,并在析构函数中将LGPerson的isa由LGKVONotifying_LGPerson指回LGPerson

image.png
我们不能在NSObject分类中直接重写-(void)dealloc方法进行监听,会改变所有NSObject子类的-(void)dealloc方法,可以在添加观察者的时候进行方法交换从而改变LGPerson的isa指向,此刻会循环递归调用,程序崩溃
image.png
循环递归调用的原因是LGPerson中没有实现dealloc方法,则会取LGPerson的父类NSObject也就是系统中的-(void)dealloc方法进行交换,NSObject中的dealloc方法就被替换为了NSObject+LGKVO分类中myDealloc方法的实现,-(void)myDealloc方法被替换为了NSObject中的dealloc方法的实现,但是其他NSObject的子类并不知道系统的dealloc方法已经被替换为myDealloc方法了,所以程序会在走[self myDealloc]中一直循环调用,自己调用自己。
image.png
image.png
解决办法在LGPerson中实现要被交换的-(void)dealloc方法,程序不会崩溃,LGViewController控制器pop后,里面的属性LGPerson也正常销毁,进入-(void)dealloc方法,可以通过交换方法监听-(void)dealloc方法从而监听到VC的销毁,从而移除自定义观察者,但是又会有别的问题,方法交换的方式就不怎么好,有太多问题了,容易造成系统的混乱
image.png
image.png

由前文知道KVO会生成派生子类LGKVONotifying_LGPerson,重写四个方法class ,setter,dealloc,isKVO,因此当观察者被移除的时候,可以在dealloc方法中将LGPerson对象中的isa指回LGPerson,在VC销毁时进入vc的dealloc方法,后进入LGKVONotifying_LGPerson的dealloc也就是lg_dealloc方法来将isa指回LGPerson


image.png

6.FBKVOController源码探索

1.保存信息_FBKVOInfo中用weak修饰下FBKVOController为了防止强引用,循环引用链条为self -> kvoCtrl -> _objectInfosMap -> infos - > info -/弱引用__weak-> self.kvoCtrl


image.png
image.png

2.监听原理


image.png

7.GNU源码分析:系统的KVO的底层代码就在此

image.png

重写setter方法[r overrideSetterFor: aPath]
添加观察者

[info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
image.png

观察的值发生改变走setter方法,消息发送给父类,判断自动开关是否打开


image.png

发送响应通知在didChangeValueForKey方法中,对change进行处理,拿到info信息进行通知


image.png
change进行setObject,oldValue和newValue进行处理,将所有消息处理完,向外界发送消息,使外界都能接收到消息,oldValue和newValue分别进行release,整个过程更加符合苹果写的系统的原生集的KVO的处理。
image.png
GNU源码有助于理解Foudation框架

问题一:LGPerson中实现-(void)dealloc方法后,和自定义添加的-(void)lg_setter方法,在LGPerson销毁的时候会走哪一个方法呢???

前文运行发现会走-(void)lg_setter方法,为什么呢?
self.person对象的地址不会发生变化,添加观察者后只是产生了一个动态子类,只是isa改变的一个类型,外部的实例对象地址是不变的,对原来的类LGPerson的-(void)dealloc方法进行了重写和覆盖,并不会再进入LGPerson的-(void)dealloc方法了


image.png

问题二:FBKVOController在进行移除观察者的时候,新建的_FBKVOInfo *info是怎么准确的移除的?很明显临时变量info在infos中是找不到的,registeredInfo为nil,所以最后[[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo]将移除个锤子,添加时是临时创建的info,移除时也是临时创建的info,是不同的一个

image.png
image.png

例如两个person是不同的内存地址,就不相等,info为空,查看NSSet中方法member:官方源码,对象被认为相等,如果isEqual:中的条件是相等的返回YES,则认为对象是相等的


image.png
image.png

重写hash和isEqual:方法来判断对象是否相等,hash匹配的是地址指针的值,两个对象相等哈希值也要相等


image.png
image.png
image.png
_FBKVOInfo中有重写hash和isEqual:方法,新建的临时变量的keyPath是相等的,能从_objectInfosMap的infos中找到info,从而进行移除
image.png
上一篇 下一篇

猜你喜欢

热点阅读