KVO官方文档学习(二)----注册键值观察

KVO官方文档

You must perform the following steps to enable an object to receive key-value observing notifications for a KVO-compliant property:

  • Register the observer with the observed object using the method addObserver:forKeyPath:options:context:.
  • Implement observeValueForKeyPath:ofObject:change:context: inside the observer to accept change notification messages.
  • Unregister the observer using the method removeObserver:forKeyPath: when it no longer should receive messages. At a minimum, invoke this method before the observer is released from memory.

你必须执行以下步骤来让一个对象能够接收一个符合KVO的属性的KVO通知:

  • 通过被观察者对象调用方法 addObserver:forKeyPath:options:context:来注册观察者。
  • 在观察者中实现方法observeValueForKeyPath:ofObject:change:context: 来接收变化通知消息。
  • 当观察者不再接收消息时通过方法 removeObserver:forKeyPath:来移除观察者注册。至少在观察者从内存中释放之前调用这个方法。

Important: Not all classes are KVO-compliant for all properties. You can ensure your own classes are KVO-compliant by following the steps described in KVO Compliance. Typically properties in Apple-supplied frameworks are only KVO-compliant if they are documented as such.

重要:并非所有类的所有属性都符合KVO标准。 您可以按照 KVO Compliance中描述的步骤确保您自己的类符合KVO标准。 通常,Apple提供的框架中的属性仅在符合KVO标准的情况下才会被记录。

个人总结:KVO三步曲:注册观察者、实现回调、移除观察者

注册成为观察者

An observing object first registers itself with the observed object by sending an addObserver:forKeyPath:options:context: message, passing itself as the observer and the key path of the property to be observed. The observer additionally specifies an options parameter and a context pointer to manage aspects of the notifications.

观察者对象通过被观察者对象发送一条addObserver:forKeyPath:options:context: 消息来首次注册自己,将自己作为观察者和要被观察的属性的key path一起传递过去。观察者还指定了一个options参数和一个context指针来管理通知的各方面。

options

The options parameter, specified as a bitwise OR of option constants, affects both the content of the change dictionary supplied in the notification, and the manner in which notifications are generated.

options参数(指定为option常量的按位或)影响着通知中提供的关于变化的字典的内容以及通知生成的方式。

You opt to receive the value of the observed property from before the change by specifying option NSKeyValueObservingOptionOld. You request the new value of the property with option NSKeyValueObservingOptionNew. You receive both old and new values with the bitwise OR of these options.

你可以通过指定option为NSKeyValueObservingOptionOld在变化发生之前来接收别观察的属性的值,指定option为NSKeyValueObservingOptionNew来请求属性的新值。也可以使用这两个option的按位或来同时接收旧值和新值。

You instruct the observed object to send an immediate change notification (before addObserver:forKeyPath:options:context: returns) with the option NSKeyValueObservingOptionInitial. You can use this additional, one-time notification to establish the initial value of a property in the observer.

你可以使用optionNSKeyValueObservingOptionInitial让被观察对象发送一条即时变化通知(在addObserver:forKeyPath:options:context:返回之前)。你可以在观察者中使用这个额外的一次性的通知来建立属性的初始值。

You instruct the observed object to send a notification just prior to a property change (in addition to the usual notification just after the change) by including the option NSKeyValueObservingOptionPrior. The change dictionary represents a prechange notification by including the key NSKeyValueChangeNotificationIsPriorKey with the value of an NSNumber wrapping YES. That key is not otherwise present. You can use the prechange notification when the observer’s own KVO compliance requires it to invoke one of the -willChange… methods for one of its properties that depends on an observed property. The usual post-change notification comes too late to invoke willChange… in time.

你通过包含optionNSKeyValueObservingOptionPrior来命令被观察对象只在出行变化之前发送一条通知(除了变化之后的通知)。变化字典通过包含键NSKeyValueChangeNotificationIsPriorKey 和将YES封装成NSNumber的值来表示一个预变化通知。这个key不代表其他意思。当观察者自己的KVO合规性要求它为其某个依赖于被观察属性的属性调用-willChange ...方法之一时,您可以使用预换通知。通常的更改后通知来得太晚,无法及时调用willChange。(。。。。。。)

context

The context pointer in the addObserver:forKeyPath:options:context: message contains arbitrary data that will be passed back to the observer in the corresponding change notifications. You may specify NULL and rely entirely on the key path string to determine the origin of a change notification, but this approach may cause problems for an object whose superclass is also observing the same key path for different reasons.

在消息addObserver:forKeyPath:options:context:中的context指针包含了相应的变化通知中将被传回给观察者的任意数据。你可以指定为NULL并完全依靠key path字符串来确定变化通知的来源,但是如果一个对象的父类出于不同的原因也在观察相同的key path,这个方法可能会出现问题。

A safer and more extensible approach is to use the context to ensure notifications you receive are destined for your observer and not a superclass.

一个更安全,更具扩展性的方法时使用上下文来确定你接收的通知时为你的观察者预定的而不是父类。

The address of a uniquely named static variable within your class makes a good context. Contexts chosen in a similar manner in the super- or subclass will be unlikely to overlap. You may choose a single context for the entire class and rely on the key path string in the notification message to determine what changed. Alternatively, you may create a distinct context for each observed key path, which bypasses the need for string comparisons entirely, resulting in more efficient notification parsing. Listing 1 shows example contexts for the balance and interestRate properties chosen this way.

类中唯一命名的静态变量的地址确定了一个良好的上下文。在超类或者子类中以相同方式选择的上下文不太可能重叠。你可以为整个类选择单个上下文,并且依赖通知消息中的key path字符串来确定改变了什么。或者,你可以为每个被观察的key path创建一个不同的上下文,来完全绕过字符串比较的需要,从而实现更有效率的通知解析。Listing 1展示了使用这种方式选择的balanceinterestRate属性的示例上下文。

Listing 1  Creating context pointers

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

Listing 2中展示了Person示例使用给定的上下文将自身注册为Account实例的balanceinterestRate属性的观察者。

Listing 2  Registering the inspector as an observer of the balance and interestRate properties

- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}

Note: The key-value observing addObserver:forKeyPath:options:context: method does not maintain strong references to the observing object, the observed objects, or the context. You should ensure that you maintain strong references to the observing, and observed, objects, and the context as necessary.

注意:KVO方法addObserver:forKeyPath:options:context:不保留对观察者对象,被观察者对象或上下文的强引用。你应该确保在必要时保留对观察者和被观察者对象以及上下文的强引用。

接收变化的通知

When the value of an observed property of an object changes, the observer receives an observeValueForKeyPath:ofObject:change:context: message. All observers must implement this method.

当一个对象的被观察属性的值发生改变时,观察者会收到一条observeValueForKeyPath:ofObject:change:context:消息。所有的观察者都必须实现这个方法。

The observing object provides the key path that triggered the notification, itself as the relevant object, a dictionary containing details about the change, and the context pointer that was provided when the observer was registered for this key path.

观察对象提供key path来触发通知,它自己作为相应的对象,一个字典包含变化的详细信息,以及为key path注册观察者时提供上下文指针。

The change dictionary entry NSKeyValueChangeKindKey provides information about the type of change that occurred. If the value of the observed object has changed, the NSKeyValueChangeKindKey entry returns NSKeyValueChangeSetting. Depending on the options specified when the observer was registered, the NSKeyValueChangeOldKey and NSKeyValueChangeNewKey entries in the change dictionary contain the values of the property before, and after, the change. If the property is an object, the value is provided directly. If the property is a scalar or a C structure, the value is wrapped in an NSValue object (as with key-value coding).

变化字典条目NSKeyValueChangeKindKey提供发生改变的类型的信息。如果被观察对象的值发生变化了,NSKeyValueChangeKindKey条目返回NSKeyValueChangeSetting。依赖于观察者被注册时指定的选项,变化字典 里的NSKeyValueChangeOldKeyNSKeyValueChangeNewKey条目包含了属性变化前后的值。如果属性是一个对象,值会直接被提供。如果属性时一个纯量或者C结构体,值会被封装成一个 NSValue对象(通过KVC)。

If the observed property is a to-many relationship, the NSKeyValueChangeKindKey entry also indicates whether objects in the relationship were inserted, removed, or replaced by returning NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, or NSKeyValueChangeReplacement, respectively.

如果被观察的属性时一个一对多的关系,NSKeyValueChangeKindKey 条目也表示关系中的对象是否被插入,移除或者通过返回NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, 或者 NSKeyValueChangeReplacement来替换它。

The change dictionary entry for NSKeyValueChangeIndexesKey is an NSIndexSet object specifying the indexes in the relationship that changed. If NSKeyValueObservingOptionNew or NSKeyValueObservingOptionOld are specified as options when the observer is registered, the NSKeyValueChangeOldKeyand NSKeyValueChangeNewKey entries in the change dictionary are arrays containing the values of the related objects before, and after, the change.

变化字典条目NSKeyValueChangeIndexesKey是一个NSIndexSet对象,用于指定变化关系中的索引。如果在注册观察者时将NSKeyValueObservingOptionNew或NSKeyValueObservingOptionOld指定为选项,则变化字典中的NSKeyValueChangeOldKey和NSKeyValueChangeNewKey条目是包含更改之前和之后的相关对象的值的数组。

The example in Listing 3 shows the observeValueForKeyPath:ofObject:change:context: implementation for the Person observer that logs the old and new values of the properties balance and interestRate, as registered in Listing 2.

Listing 3中的示例显示了observeValueForKeyPath:ofObject:change:context: 用于记录属性balance和interestRate的旧值和新值的Person观察者的实现,如Listing 2中所示

Listing 3  Implementation of observeValueForKeyPath:ofObject:change:context:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something with the balance…
 
    } else if (context == PersonAccountInterestRateContext) {
        // Do something with the interest rate…
 
    } else {
        // Any unrecognized context must belong to super
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                               context:context];
    }
}

If you specified a NULL context when registering an observer, you compare the notification’s key path against the key paths you are observing to determine what has changed. If you used a single context for all observed key paths, you first test that against the notification’s context, and finding a match, use key path string comparisons to determine what specifically has changed. If you have provided a unique context for each key path, as demonstrated here, a series of simple pointer comparisons tells you simultaneously whether or not the notification is for this observer, and if so, what key path has changed.

当注册观察者时如果指定了一个NULL上下文,则将通知的key path和正在监听的key paths进行比较以确定改变的内容。如果您对所有观察到的key path使用了单个上下文,则首先针对通知的上下文对其进行测试,然后查找匹配项,使用key path字符串比较来确定具体更改的内容。 如果您为每个key path提供了唯一的上下文,如此处所示,一系列简单的指针比较会同时告诉您通知是否适用于此观察者,如果是,则更改了哪个key path。

In any case, the observer should always call the superclass’s implementation of observeValueForKeyPath:ofObject:change:context: when it does not recognize the context (or in the simple case, any of the key paths), because this means a superclass has registered for notifications as well.

在任何情况下,观察者在无法识别上下文时应该总是调用父类的实现(或者简单的情况下,任意的key path),因为这意味着父类也已经注册了通知。

Note: If a notification propagates to the top of the class hierarchy, NSObject throws an NSInternalInconsistencyException because this is a programming error: a subclass failed to consume a notification for which it registered.

注意:如果一个通知传到类层次结构的顶部,NSObject会抛出一个NSInternalInconsistencyException 异常,因为这是一个编程错误:一个子类无法去使用它注册的通知。

移除作为观察者的对象

You remove a key-value observer by sending the observed object a removeObserver:forKeyPath:context: message, specifying the observing object, the key path, and the context. The example in Listing 4 shows Person removing itself as an observer of balance and interestRate.

通过向被观察者对象发送一个removeObserver:forKeyPath:context:消息并指定观察对象,key path和上下文来移除键值观察。Listing 4的示例展示了Person移除自身作为balanceinterestRate的观察者。

Listing 4  Removing the inspector as an observer of balance and interestRate

- (void)unregisterAsObserverForAccount:(Account*)account {
    [account removeObserver:self
                 forKeyPath:@"balance"
                    context:PersonAccountBalanceContext];
 
    [account removeObserver:self
                 forKeyPath:@"interestRate"
                    context:PersonAccountInterestRateContext];
}

After receiving aremoveObserver:forKeyPath:context:message, the observing object will no longer receive any observeValueForKeyPath:ofObject:change:context:messages for the specified key path and object.

当收到一条removeObserver:forKeyPath:context:消息后,观察对象将不再接收任何针对指定key path和对象的observeValueForKeyPath:ofObject:change:context:消息。

When removing an observer, keep several points in mind:

当移除一个观察者时,要记住以下几点:

  • Asking to be removed as an observer if not already registered as one results in an NSRangeException. You either call removeObserver:forKeyPath:context: exactly once for the corresponding call to addObserver:forKeyPath:options:context:, or if that is not feasible in your app, place the removeObserver:forKeyPath:context:call inside a try/catch block to process the potential exception.

如果还没有注册观察者就被要求作为观察者被移除会导致NSRangeException异常。对相应的 addObserver:forKeyPath:options:context:调用只可以调用一次removeObserver:forKeyPath:context:,或者如果在你的APP中不可行的话,把removeObserver:forKeyPath:context:调用放在try/catch代码块中去处理潜在的异常。
个人总结:必须要先注册观察者才能调用移除观察者方法,如果还未注册就调用移除方法会出现异常。

  • An observer does not automatically remove itself when deallocated. The observed object continues to send notifications, oblivious to the state of the observer. However, a change notification, like any other message, sent to a released object, triggers a memory access exception. You therefore ensure that observers remove themselves before disappearing from memory.

观察者在销毁时不会自动移除自己。被观察的对象继续发送通知,无视观察者的状态。然而,变化通知像其他消息一样发送给已释放的对象,会触发内存访问异常。因此在观察者从内存中消失之前要确定移除自身。
个人总结:观察者对象在释放之前一定要手动移除观察,否则可能会导致内存泄漏。

The protocol offers no way to ask an object if it is an observer or being observed. Construct your code to avoid release related errors. A typical pattern is to register as an observer during the observer’s initialization (for example in init or viewDidLoad) and unregister during deallocation (usually in dealloc), ensuring properly paired and ordered add and remove messages, and that the observer is unregistered before it is freed from memory.

协议没有提供什么方式去查询一个对象是否是观察者或者被观察者。组织代码来避免发生相关的错误。一个典型的模式是在观察者初始化时注册成为观察者(例如在init或viewDidLoad中),在销毁时取消注册(通常在dealloc中),确保正确的成对和有顺序的添加和移除消息,并且在观察者从内存中释放之前取消注册。
个人总结:观察者的注册和移除必须成对存在,并且要先注册后移除,需要开发者自身进行检查。

你可能感兴趣的:(KVO官方文档学习(二)----注册键值观察)