美文网首页
KVO使用以及原理分析

KVO使用以及原理分析

作者: 川少叶 | 来源:发表于2018-10-28 19:27 被阅读6次

    基础使用

    使用KVO需要三个步骤:

    1. 在观察者中,调用被观察者的addObserver:forKeyPath:options:context:进行注册
    2. 在观察者中实现observeValueForKeyPath:ofObject:change:context:方法
    3. 在观察者中使用removeObserver:forKeyPath:移除KVO,一般可以在dealloc方法中移除,否则会导致Crash

    1 注册观察者

    比方说ViewController是观察者,person是其属性。ViewController需要监听person中的age属性

    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
    [self.person addObserver:self forKeyPath:@"numArray" options:options context:nil];
    

    options是一个NS_OPTIONS枚举,可以决定以下内容

    • 通知发出的时机
    • observeValueForKeyPath:ofObject:change:context:方法中,字典参数change字典中包含哪些值

    参数options有四个值

    1. NSKeyValueObservingOptionNew,change字典中应该包含改变后的新值

    2. NSKeyValueObservingOptionOld,change字典中应该包含改变前的旧值

    3. NSKeyValueObservingOptionInitialaddObserver:forKeyPath:options:context:消息被发出去后,甚至不用等待这个消息返回,监听者对象会马上收到一个通知。这种通知只会发送一次,你可以利用这种“一次性“的通知来确定要监听属性的初始值。当同时制定这3个选项时,这种通知的change字典中只会包含新值,而不会包含旧值。虽然这时候的新值实际上是改变前的'旧值',但是这个值对于监听者来说是新的。

    4. NSKeyValueObservingOptionPrior:当指定了这个选项时,在被监听的属性被改变前,监听者对象就会收到一个通知(一般的通知发出时机都是在属性改变后,虽然change字典中包含了新值和旧值,但是通知还是在属性改变后才发出),这个通知会包含一个NSKeyValueChangeNotificationIsPriorKeykey,其对应的值为一个NSNumber类型的YES。当同时指定该值、new和old的话,change字典会包含旧值而不会包含新值。

    2 接收通知

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<nskeyvaluechangekey, id> *)change context:(nullable void *)context;</nskeyvaluechangekey, id>
    

    关于change参数,它是一个字典,有五个常量作为它的键:

    1. NSKeyValueChangeKindKey:指明了变更的类型,值为NSKeyValueChange枚举中的某一个,类型为NSNumber。
      • 一般情况下返回的都是第一个NSKeyValueChangeSetting
      • 如果监听的属性是一个集合对象的话,当这个集合中的元素被插入,删除,替换时,就会分别返回NSKeyValueChangeInsertionNSKeyValueChangeRemovalNSKeyValueChangeReplacement
    enum {
     NSKeyValueChangeSetting = 1,
     NSKeyValueChangeInsertion = 2,
     NSKeyValueChangeRemoval = 3,
     NSKeyValueChangeReplacement = 4
    };
    typedef NSUInteger NSKeyValueChange;
    
    1. NSKeyValueChangeNewKey:被监听属性改变后新值的key。当监听属性为一个集合对象,且NSKeyValueChangeKindKey不为NSKeyValueChangeSetting时,该值返回的是一个数组,包含插入,替换后的新值(删除操作不会返回新值)。

    2. NSKeyValueChangeOldKey:被监听属性改变前旧值的key。当监听属性为一个集合对象,且NSKeyValueChangeKindKey不为NSKeyValueChangeSetting时,该值返回的是一个数组,包含删除,替换前的旧值(插入操作不会返回旧值)

    3. NSKeyValueChangeIndexesKey:如果NSKeyValueChangeKindKey的值为NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, 或者 NSKeyValueChangeReplacement,这个键的值是一个NSIndexSet对象,包含了增加,移除或者替换对象的index

    4. NSKeyValueChangeNotificationIsPriorKey:如果注册监听者是options中指明了NSKeyValueObservingOptionPrior,change字典中就会带有这个key,值为NSNumber类型的YES.

    3. 移除监听

    当一个监听者完成了它的监听任务之后,就需要注销(移除)监听者,调用以下2个方法来移除监听。通常会在-dealloc方法或者observeValueForKeyPath:ofObject:change:context:方法中移除。

    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context
    或者
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    

    有几点需要注意的:

    当你向一个不是监听者的对象发送remove消息的时候(也可能是,你发送remove消息时,接受消息的对象已经被remove了一次,或者在注册为监听者前就调用了remove),xcode会抛出一个NSRangeException异常,所以,保险的做法是,把remove操作放在try/catch中。

    一个监听者在其被销毁时,并不会自己注销监听,而给一个已经销毁的监听者发送通知,会造成野指针错误。所以至少保证,在监听者被释放前,将其监听注销。保证有一个add方法,就有一个remove方法。

    4 触发KVO

    触发KVO的方式包括:

    • 点语法
    • set方法
    • kvc
    • 如果监听的是集合属性,以数组为例,得使用被观察者mutableArrayValueForKey,进行操作
    [[self.person mutableArrayValueForKey:@"numArray"] addObject:@(randNum)];
    

    KVO原理

    当某个对象第一次被观察时,系统就会在运行期动态地创建该类的一个子类类对象(类名就是在该类的前面加上NSKVONotifying_ 前缀),被观察对象的isa指针就指向这个类对象。这个子类类对象,会有如下的变化:

    • 被观察属性的set方法被修改
    • 修改class实例方法,让其返回原始的类对象
    • dealloc_isKVOA
      KVO之后类的关系.png

    参考

    https://www.jianshu.com/p/badf5cac0130
    Demo地址:https://github.com/xiaoLong1010/DeepObjectiveC/tree/master/03-KVO

    相关文章

      网友评论

          本文标题:KVO使用以及原理分析

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