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

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);
iOS-底层原理21-KVO(下)_第1张图片
image.png

iOS-底层原理21-KVO(下)_第2张图片
image.png
  • 将llvm严格校验参数个数关闭,解决objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)方法报错


    iOS-底层原理21-KVO(下)_第3张图片
    image.png
  • 此时存在一个问题,系统的观察者在观察前后一直self.person.class一致指向LGPerson,而此时自定义的观察者self.person.class指向LGKVONotifying_LGPerson,此时需要重写class方法


    iOS-底层原理21-KVO(下)_第4张图片
    image.png

    iOS-底层原理21-KVO(下)_第5张图片
    image.png

    重写class方法:class_getSuperclass(object_getClass(self))


    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

iOS-底层原理21-KVO(下)_第6张图片
image.png

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


iOS-底层原理21-KVO(下)_第7张图片
image.png

此时执行程序发现无法找到setNickName:方法,为什么呢?分析原因,取了两次父类.super_class = class_getSuperclass([self class])中[self class]调用lg_class方法,class_getSuperclass(object_getClass(self))又取一次父类,即LGKVONotifying_LGPerson的父类的父类,并不存在setNickName:所以报unrecognized selector
iOS-底层原理21-KVO(下)_第8张图片
image.png

iOS-底层原理21-KVO(下)_第9张图片
image.png

正确的写法为.super_class = [self class]LGKVONotifying_LGPerson的父类LGPerson中查找setNickName:方法
iOS-底层原理21-KVO(下)_第10张图片
image.png
  • objc_msgSendSuper消息发送的好处:通用,封装,解决了依赖,并不依赖于类LGPerson或NSObject,都不用关注这个类是NSObject还是LGPerson
    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);
iOS-底层原理21-KVO(下)_第11张图片
image.png
  • 通知VC中的方法,属性值发生了变化,先要保存VC,在分类中保存VC,用到runtime关联对象,保存观察者VC控制器,setNickName:中收到nickName的值变化后,发送消息通知观察者控制器VC


    image.png

    iOS-底层原理21-KVO(下)_第12张图片
    image.png
  • 添加关联对象是否产生循环引用了?没有,因为关联对象是存放在一个哈希表中,并没有进行持有,一一对应关系的保存,控制器VCpop后还是能正常进入dealloc方法,并没有产生循环引用



    iOS-底层原理21-KVO(下)_第13张图片
    image.png

    iOS-底层原理21-KVO(下)_第14张图片
    image.png

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

  • 1.新建LGKVOInfo保存观察信息类,保存观察者信息


    iOS-底层原理21-KVO(下)_第15张图片
    image.png

    iOS-底层原理21-KVO(下)_第16张图片
    image.png
  • 2.观察一个属性的情况
    I.添加观察者,观察属性nickName变化


    iOS-底层原理21-KVO(下)_第17张图片
    image.png

    II.保存观察者信息,方便第三步进行消息发送到观察者控制器


    iOS-底层原理21-KVO(下)_第18张图片
    image.png

    III.对新旧值进行处理,发送给观察者
    iOS-底层原理21-KVO(下)_第19张图片
    image.png
  • 3.观察多个属性值的情况,同事观察nickName和kcName


    iOS-底层原理21-KVO(下)_第20张图片
    image.png

    iOS-底层原理21-KVO(下)_第21张图片
    image.png

    iOS-底层原理21-KVO(下)_第22张图片
    image.png

    观察两个属性,observerArr中存在两个info


    image.png

3.移除观察者

iOS-底层原理21-KVO(下)_第23张图片
image.png

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

4.自定义函数式KVO

iOS-底层原理21-KVO(下)_第24张图片
image.png
iOS-底层原理21-KVO(下)_第25张图片
image.png
iOS-底层原理21-KVO(下)_第26张图片
image.png
image.png
iOS-底层原理21-KVO(下)_第27张图片
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指向,此刻会循环递归调用,程序崩溃
iOS-底层原理21-KVO(下)_第28张图片
image.png

循环递归调用的原因是LGPerson中没有实现dealloc方法,则会取LGPerson的父类NSObject也就是系统中的-(void)dealloc方法进行交换,NSObject中的dealloc方法就被替换为了NSObject+LGKVO分类中myDealloc方法的实现,-(void)myDealloc方法被替换为了NSObject中的dealloc方法的实现,但是其他NSObject的子类并不知道系统的dealloc方法已经被替换为myDealloc方法了,所以程序会在走[self myDealloc]中一直循环调用,自己调用自己。
image.png

iOS-底层原理21-KVO(下)_第29张图片
image.png

解决办法在LGPerson中实现要被交换的-(void)dealloc方法,程序不会崩溃,LGViewController控制器pop后,里面的属性LGPerson也正常销毁,进入-(void)dealloc方法,可以通过交换方法监听-(void)dealloc方法从而监听到VC的销毁,从而移除自定义观察者,但是又会有别的问题,方法交换的方式就不怎么好,有太多问题了,容易造成系统的混乱
iOS-底层原理21-KVO(下)_第30张图片
image.png

iOS-底层原理21-KVO(下)_第31张图片
image.png

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


iOS-底层原理21-KVO(下)_第32张图片
image.png

6.FBKVOController源码探索

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


iOS-底层原理21-KVO(下)_第33张图片
image.png

iOS-底层原理21-KVO(下)_第34张图片
image.png

2.监听原理


iOS-底层原理21-KVO(下)_第35张图片
image.png

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

image.png

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

[info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
iOS-底层原理21-KVO(下)_第36张图片
image.png

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


iOS-底层原理21-KVO(下)_第37张图片
image.png

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


iOS-底层原理21-KVO(下)_第38张图片
image.png

change进行setObject,oldValue和newValue进行处理,将所有消息处理完,向外界发送消息,使外界都能接收到消息,oldValue和newValue分别进行release,整个过程更加符合苹果写的系统的原生集的KVO的处理。
iOS-底层原理21-KVO(下)_第39张图片
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,是不同的一个

iOS-底层原理21-KVO(下)_第40张图片
image.png

iOS-底层原理21-KVO(下)_第41张图片
image.png

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


iOS-底层原理21-KVO(下)_第42张图片
image.png

iOS-底层原理21-KVO(下)_第43张图片
image.png

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


iOS-底层原理21-KVO(下)_第44张图片
image.png

iOS-底层原理21-KVO(下)_第45张图片
image.png

iOS-底层原理21-KVO(下)_第46张图片
image.png

_FBKVOInfo中有重写hash和isEqual:方法,新建的临时变量的keyPath是相等的,能从_objectInfosMap的infos中找到info,从而进行移除
iOS-底层原理21-KVO(下)_第47张图片
image.png

你可能感兴趣的:(iOS-底层原理21-KVO(下))