解剖iOS中KVO的原理

作者: roger_Hunter | 来源:发表于2018-06-12 17:24 被阅读422次

            为什么还要讲KVO的原理呢,如果想了解KVO原理,上网一查,可以搜出一大堆文章,不是更好?以前我也是上网一查,大概知道了怎么回事,但是呢,过一段时间,你肯定忘了,不知道你是否有同感,原因?

    1.  你用零碎的时间学习零碎的知识,不能形成有意识的系统知识,这样只是一场过客,时间一长,你还得去重新查找,浪费时间和精力不说,你觉的有意思吗?

    2. 网上的文章说的不全,比如说,说了对象,但是没有说容器;说了基本使用,没有说底层的实现等

    3. 没有相关代码来佐证等

            而本篇文章试图从最简单的用法说起,由浅入深,覆盖对象和容器,底层机制

    一.    一般基本的使用,新建一个对象Animal,在头文件中加一个name的属性

    图1

    同时在ViewControler中创建Animal的对象,同时监听该对象的name属性

    图2

    结果:点击vc,得到结果如下:

    图3

    这是一个对象完整的使用基本KVO的流程

    说明:

            NSKeyValueObservingOptionNew              // 返回新值

            NSKeyValueObservingOptionOld                // 返回旧值

            NSKeyValueObservingOptionInitial             // 注册的时候就会发一次通知,改变的时候也会发通知

            NSKeyValueObservingOptionPrior             // 改变之前发一次,改变之后发一次

    二. 上面是name改变值都会发送通知,但是现在我只要满足特定条件才发送通知,另外情况都不需要发送通知,怎么做呢?

        iOS提供了2种模式给我们选择,一种是 默认的自动模式,一种是手动模式

        2.1 手动模式

              在Animal的m文件中重载实现automaticallyNotifiesOberversOfName函数,返回NO即可,结果是没有返回值

    图4

            然后我想要只在touchesBegan函数中才改变值才通知,另外情况都不需要通知,如下

    图5

            为什么需要willChange和didChange 2个函数呢?因为NSKeyValueObservingOptions的选项有返回旧值和新值,所以willChange对应返回旧值,didChange返回新值,也就是说这2个是成双成对出现的

    三. 属性依赖,如果在Animal对象里有另外一个对象,然后要监听那个对象的属性呢,新增一个cat对象,增加age的属性,在Animal里增加一个Cat对象,同时在ViewController里监听Cat对象的age属性

    图5

    另外一种情况是:如果在cat对象中有很多属性,这样在监听的时候是不是要写很多监听的代码呢,是否有简单的方法来统一监听所有的属性呢??

    eg:

        [self.a addObserver:self forKeyPath:@"cat.age" options:NSKeyValueObservingOptionNew context:nil];

        [self.a addObserver:self forKeyPath:@"cat.kind" options:NSKeyValueObservingOptionNew context:nil];

        ....

    iOS提供了一个方法来返回指定的属性容器,达到只要监听对象的目的

    eg: 在ViewController中

    [self.a addObserver:self forKeyPath:@"cat" options:NSKeyValueObservingOptionNew context:nil];

    相应的在Animal的m文件中重载keyPathsForValuesAffectingValueForKey函数

    四. 如果我要观察的是容器呢,比如NSMutableArray呢,我在Animal头文件中增加一个NSMutableArray的属性,在ViewController中进行addObject操作

    图6 图7

    运行发现,像array里增加值,并不能发送通知,为什么??

    结论:KVO监听的是属性的set方法,不是对象的变量,而addObject方法明显不是set方法,所以不会触发通知

    如果NSMutableArray的addObject方法还不能证明的话,那么再通过一个例子来证明

    在Animal头文件中声明一个name的变量

    图8

    在ViewController中监听name属性

    图9

    运行后发现,没有发送通知

    五. 底层实现

        5.1 当调用addObserver函数的时候,系统运行时自动帮你把Animal类替换为了 NSKVONotifying_Animal类,同时重写了set方法,当我们查看addObserver函数的时候,可以看到该函数在NSObject分类下@interfaceNSObject(NSKeyValueObserverRegistration)

    这里大家要理清一个概念:方法跟类有关系,成员变量跟对象有关系

       5.2 新增一个NSObject的一个分类来替换系统的分类

    图10

    疑问:为什么要添加setName方法?自定义的子类没有该方法?是的,没有该方法,子类继承于父类,父类有,但是子类没有

    六. 容器实现监听,使用KVC技术

    iOS提供了mutableArrayValueForKey等多个KVC的方法来生成一个KVO的array,来实现addObject方法的监听

    但是,我发现count不是数组的属性,不能进行监听修改,以上就是本文的内容,有不当之处欢迎指出。

    七. 课后作业

          7.1 不知道大家有没发现,对于change返回的kind值,对象和容器是不一样的,一个是1,一个是2,那么是否还有第三个,第四个... 呢,这个kind代表什么意思呢?


    对象 容器

        7.2. 关于在图10中的keyMethod方法,里面分别去调用父类的setName,同时调用observer函数,为什么不直接调用子类的setName方法??

    关于这些问题,我会在下期的KVC详解中解答

    相关文章

      网友评论

      • 一往情深_b560:Class myclass = [self class];
        object_setClass(self, class_getSuperClass(myclass));
        objc_msgSend(self, @selector(setName:), newName);

        NSObject *observer = objc_getAllociatedObject(self, @"HS_observer");

        这里isa不是已经修改为指向父类了吗, 父类怎么会有HS_observer属性呢,上面不是把HS_observer和子类关联的吗,

      本文标题:解剖iOS中KVO的原理

      本文链接:https://www.haomeiwen.com/subject/iubleftx.html