整理了一下关于KVO的姿势

作者: 卖萌凉 | 来源:发表于2015-07-15 23:11 被阅读4481次

    其实是第一次看Key-Value Observing Programming GuideKey-Value Coding Programming Guide。虽然以前也写过一点KVO的代码,但是当时为了快点赶晚项目,随便看了看示例代码就贴到工程里了。是一个不好的习惯呢。所以还是把一些细节弄弄清楚,把姿势整理一下吧^ ^


    设立一个观察者



    KVO(Key-value observing)提供了一种机制,当某个对象的某个特定的property被改动时,它能够允许别的对象接到这个通知。

    实现KVO的前提条件就是,这个类和它的这个property是KVC compliant的。对于这个类来说,valueForKey:setValue:forKey:这两个方法被合理的实现(NSObject对这两个方法有了默认的实现);如果这个property是一个attribute或者to-one relationship的话,要满足以下几个条件的至少一个(其中,key表示这个property对应的key):

    1. 这个类以key为名,声明了一个property;
    2. 这个类实现了以key为名的accessor方法;
    3. 这个类以key或者_key为名,声明了一个实例变量。

    如果这个property是一个to-many relationship,情况会更复杂,还是把官方文档搬出来吧。

    对一个property设定一个观察者需要这样几个步骤:

    一. 使用addObserver:forKeyPath:options:context:方法,对需要观察的property注册一个观察者

    这时,观察者对象和被观察的对象之间会建立联系,这种联系是针对于对象的,而不是针对于类的。

    其中可以指定options参数:

    • NSKeyValueObservingOptionNew:当options中包括了这个参数的时候,观察者收到的change参数中就会包含NSKeyValueChangeNewKey和它对应的值,也就是说,观察者可以得知这个property在被改变之后的新值。
    • NSKeyValueObservingOptionOld:和NSKeyValueObservingOptionNew的意思类似,当包含了这个参数的时候,观察者收到的change参数中就会包含NSKeyValueChangeOldKey和它对应的值。
    • NSKeyValueObservingOptionInitial:当包含这个参数的时候,在addObserver的这个过程中,就会有一个notification被发送到观察者那里,反之则没有。
    • NSKeyValueObservingOptionPrior:当包含这个参数的时候,在被观察的property的值改变前和改变后,系统各会给观察者发送一个change notification;在property的值改变之前发送的change notification中,change参数会包含NSKeyValueChangeNotificationIsPriorKey并且值为@YES,但不会包含NSKeyValueChangeNewKey和它对应的值。

    可以指定多个NSKeyValueObservingOptions,将他们用“或”连接后,作为options参数。

    可以将任意对象作为context参数,它会和观察者实现的observeValueForKeyPath:ofObject:change:context:方法中的context参数指向同一个对象。

    二. 观察者必须实现observeValueForKeyPath:ofObject:change:context:方法,并定义观察者应该如何响应change notification

    在这个方法中,change参数会传入一个NSDictionary,代表了与property的值变化相关的信息。其中可能会有这样几个键值对:

    • NSKeyValueChangeKindKey:这是change中永远会包含的键值对,它的值时一个NSNumber对象,具体的数值有NSKeyValueChangeSettingNSKeyValueChangeInsertionNSKeyValueChangeRemovalNSKeyValueChangeReplacement这几个,其中后三个是针对于to-many relationship的。

    • NSKeyValueChangeNewKey:只有当addObserver的时候在optional参数中加入NSKeyValueObservingOptionNew,这个键值对才会被change参数包含;它表示这个property改变后的新值。

    • NSKeyValueChangeNewOld:只有当addObserver的时候在optional参数中加入NSKeyValueObservingOptionOld,这个键值对才会被change参数包含;它表示这个property改变前的值。

    • NSKeyValueChangeIndexesKey:当被观察的property是一个ordered to-many relationship时,这个键值对才会被change参数包含;它的值是一个NSIndexSet对象。

    • NSKeyValueChangeNotificationIsPriorKey:只有当addObserver的时候在optional参数中加入NSKeyValueObservingOptionPrior,这个键值对才会被change参数包含;它的值是@YES

    三. 当被观察的property的值发生变化的时候,或者它依赖的某一个key的值发生变化的时候,observeValueForKeyPath:ofObject:change:context:方法会自动被调用

    这里的改变被观察的property的值,指的应该是这样几种方式中的一种:

    1. 调用key-value compliant的accessor方法;
    2. 使用key-value coding方法,如setValue:forKey:insertValue:inPropertyWithKey:
    3. 使用mutableArrayValueForKey:取得一个代理对象,并操作这个代理对象。

    所以如果仅仅是改变了某个property所生成的instance variable的值,自动的change notification是不会发送的。

    四. 当不再需要这个观察者的时候,需要调用removeObserver:forKeyPath:或者removeObserver:forKeyPath:context:方法,移除这个观察者

    应该尽量使用removeObserver:forKeyPath:context:方法,因为如果当同样的observer和同样的key path被多次注册,但是每次注册使用的是不同的context,这时如果使用removeObserver:forKeyPath:方法,它就需要猜测到底移除哪一个观察者,当然这很可能猜错。

    那些在addObserver:forKeyPath:options:context:中指定的对象,必须在deallocate之前被移除掉。


    手动发送change notification



    按照上面说的方法,为某个property设立一个观察者,这个观察者就会收到系统自动发送的change notification。然而,第三方程序员也可以手动发送change notification。手动发送change notification可以更自由的控制通知发送的逻辑。

    如果一个类想要实现手动的change notification发送,则必须重写NSObject实现的automaticallyNotifiesObserversForKey:方法,并对需要实现手动发送的key返回NO,其余则调用super。

    官方示例代码:

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
        BOOL automatic = NO;
        if ([theKey isEqualToString:@"openingBalance"]) {
            automatic = NO;
        }
        else {
            automatic = [super automaticallyNotifiesObserversForKey:theKey];
        }
        return automatic;
    }
    

    然后在property的值改变之前调用willChangeValueForKey:,在值改变之后调用didChangeValueForKey:。当然,在什么样的情况下才调用这两个方法,是由第三方程序的逻辑决定的。

    如果一个操作造成了多个key的值的改变,则willChangeValueForKey:didChangeValueForKey:必须嵌套着调用。

    官方示例代码:

    - (void)setOpeningBalance:(double)theBalance {
        [self willChangeValueForKey:@"openingBalance"];
        [self willChangeValueForKey:@"itemChanged"];
        _openingBalance = theBalance;
        _itemChanged = _itemChanged+1;
        [self didChangeValueForKey:@"itemChanged"];
        [self didChangeValueForKey:@"openingBalance"];
    }
    

    willChangeValueForKey:didChangeValueForKey:调用时都会调用valueForKey:,并把得到的结果分别当成old value和new value,以告知观察者。

    做了个小实验,发现在自动发送change notification的情况下,willChangeValueForKey:didChangeValueForKey:也会被调用,看来系统也是通过这两个方法来发送通知的。

    如果手动发送change notification的property是ordered to-many relationship,则不仅要指定被改变的key,还要指定改变的类型和index。类型用NSKeyValueChange来表示。


    注册dependent keys



    对于to-one relationship的property,重写keyPathsForValuesAffectingValueForKey:或者keyPathsForValuesAffecting<Key>方法可以定义这个key所依赖的一系列key。但是这个方法仅限于to-one relationship的property。

    官方示例代码:

    + (NSSet *)keyPathsForValuesAffectingFullName {
        return [NSSet setWithObjects:@"lastName", @"firstName", nil];
    }
    

    而对于to-many relationship,要么手动观察每一个依赖的key,要么利用Core Data来完成这个任务。

    相关文章

      网友评论

      • 山林间迷雾能不能当障眼法的内容:NSKeyValueChangeIndexesKey:当被观察的property是一个ordered to-many relationship时,这个键值对才会被change参数包含;它的值是一个NSIndexSet对象。
        这什么意思 能否细说一下

      本文标题:整理了一下关于KVO的姿势

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