KVO原理

作者: 一个半吊子工程师 | 来源:发表于2020-10-29 17:15 被阅读0次

    概述

    KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。

    KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。

    KVO可以监听单个属性的变化,也可以监听集合对象的变化。通过KVC的mutableArrayValueForKey:等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO监听的方法。集合对象包含NSArrayNSSet

    KVO 的原理

     1: 动态生成子类 : NSKVONotifying_xxx
     2: 观察的是 setter
     3: 动态子类重写了很多方法 setNickName (setter) class dealloc _isKVOA
     4: 移除观察的时候 isa 指向回来
     5: 动态子类不会销毁
    

    iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

    当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,
    子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部
    又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。

    image.png

    如何手动触发KVO

    被监听的属性的值被修改时,就会自动触发KVO。
    如果想要手动触发KVO,则需要我们自己调用willChangeValueForKeydidChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO
    ,并且这两个方法缺一不可。
    在viewController

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        self.p1.age = 10;
        [self.p1 willChangeValueForKey:@"nickName"];
        self.p1.nickName = @"nickName1";
        [self.p1 didChangeValueForKey:@"nickName"];
    }
    

    或者在model

    - (void)setAge:(int)age {
        if (age != _age) {
            [self willChangeValueForKey:@"age"];
            _age = age;
            [self didChangeValueForKey:@"age"];
        }
    }
    

    如果想控制当前对象的自动调用过程,也就是由上面两个方法发起的KVO调用,则可以重写下面方法。方法返回YES则表示可以调用,如果返回NO则表示不可以调用。

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
        BOOL automatic = NO;
        return automatic;
    }
    
    image.png

    基础使用

    使用KVO分为三个步骤:

    1、通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
    2、在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
    3、当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。

    注册方法

    在注册观察者时,可以传入options参数,参数是一个枚举类型。如果传入NSKeyValueObservingOptionNewNSKeyValueObservingOptionOld表示接收新值和旧值,默认为只接收新值。如果想在注册观察者后,立即接收一次回调,则可以加入NSKeyValueObservingOptionInitial枚举。

    还可以通过方法context传入任意类型的对象,在接收消息回调的代码中可以接收到这个对象,是KVO中的一种传值方式。

    在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash

    监听方法

    观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crashchange字典中存放KVO属性相关的值,根据options时传入的枚举来返回。枚举会对应相应key来从字典中取出值,例如有NSKeyValueChangeOldKey字段,存储改变之前的旧值。

    change中还有NSKeyValueChangeKindKey字段,和NSKeyValueChangeOldKey是平级的关系,来提供本次更改的信息,对应NSKeyValueChange枚举类型的value。例如被观察属性发生改变时,字段为NSKeyValueChangeSetting

    如果被观察对象是集合对象,在NSKeyValueChangeKindKey字段中会包含NSKeyValueChangeInsertionNSKeyValueChangeRemovalNSKeyValueChangeReplacement的信息,表示集合对象的操作方式。

    缺点

    苹果提供的KVO自身存在很多问题,首要问题在于,KVO如果使用不当很容易崩溃。例如重复add和remove导致的CrashObserver被释放导致的崩溃,keyPath传错导致的崩溃等。

    在调用KVO时需要传入一个keyPath,由于keyPath是字符串的形式,所以其对应的属性发生改变后,字符串没有改变容易导致Crash。我们可以利用系统的反射机制将keyPath反射出来,这样编译器可以在@selector()中进行合法性检查。

    NSStringFromSelector(@selector(isFinished))
    KVO是一种事件绑定机制的实现,在keyPath对应的值发生改变后会回调对应的方法。这种数据绑定机制,在对象关系很复杂的情况下,很容易导致不好排查的bug。例如keyPath对应的属性被调用的关系很复杂,就不太建议对这个属性进行KVO,可以想一下RAC的信号脑补一下。
    demo
    参考资料

    相关文章

      网友评论

          本文标题:KVO原理

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