键值观察提供了一种机制,允许对象在其他对象的特定属性值修改是收到通知。 这对于应用程序中的model层和controller层之间的通信特别有用。 (在OS X中,controller层绑定技术在很大程度上依赖于键值观察。)controller对象通常会观察model对象的属性,View对象通过controller观察Model对象的属性。 另外,Model对象可以观察其他Model对象(通常用于确定依赖值何时发生变化),甚至可以观察其本身(再次确定依赖值何时发生变化)。
您可以观察属性,包括简单属性,一对一关系和多对多关系。 多对多关系的观察者会被告知所做更改的类型以及参与更改的对象。

Registering for Key-Value Observing

• 使用 addObserver:forKeyPath:options:context:. 方法注册观察者的观察对象
• 在观察者内部实现 observeValueForKeyPath:ofObject:change:context: 来接受变化的消息通知
• 使用 removeObserver:forKeyPath: 来取消观察者,当不应该收到消息的时候。应该在观察者被销毁前调用此方法。

Note: 不是所有类的所有属性都支持KVO

Registering as an Observer 注册为观察者

观察对象首先通过发送一个 addObserver:forKeyPath:options:context: 消息向注册的对象注册自己。将自己作为观察者和要观察的属性的关keypath. 观察者另外指定一个option参数和一个上下文指针context来管理通知的各个方面。

Options 参数

Options 参数(指定二进制 OR 作为操作常量)影响通知中提供的更改字典的内容以及生成通知的方式。

您可以通过指定选项NSKeyValueObservingOptionOld,在更改前接收观察属性的值。您可以通过选项NSKeyValueObservingOptionNew请求属性的新值。您可以通过这些选项的OR值来获得旧值和新值。

您可以命令观察对象使用NSKeyValueObservingOptionInitial发送立即更改的通知(此通知在addObserver:forKeyPath:options:context:方法返回之前)。您可以使用此额外的一次性通知来确定观察者中属性的初始值。使用此属性可以在addObserver:forKeyPath:options:context方法前获取初始值。

通过包含选项NSKeyValueObservingOptionPrior,您可以指示观察对象在属性更改之前发送通知(除了通常在更改后发出通知之外)。更改字典这个操作可以通过包含一个选项为NSKeyValueChangeNotificationIsPriorKey和包含一个值为YES的NSNumber来表示预变化prechange通知。这个key原本是不存在的。当观察者自身的 KVO 规则 要求它为依赖于观察到的属性的属性之一调用 willChange… 方法时, 可以使用 prechange 通知。通常的更改后通知来得太迟,无法及时调用willChange …。

Context 上下文

addObserver:forKeyPath:options:context 中的上下文指针:是消息中包含将在相应的更改通知中传递回观察者的任意数据。您可以指定 NULL 并完全依赖于kay path的字符串来确定更改通知的来源, 但是,此方法可能会导致对象的问题, 他的superclass也因不同的原因而观察相同的kaypath。

所以,确保你收到的通知是給观察者自己而不是給她的superclass,使用 context 指针是一个安全而且可扩展的。

Note: addObserver:forKeyPath:options:context: 方法不会对正在观察的对象,以观察对象和上下文强持有(Strong references)。你需要自己确认这些对象的强持有。

接收一个变化的通知Receiving Notification of a Change

当对象的观察属性值发生变化时, 观察者收到 observeValueForKeyPath:ofObject:change:context: 消息。所有者都必须实现此方法。

观察对象提供的对象有:触发通知的keypath,作为关联对象的它自己本身,包含更改详细信息的字典,以及上下文指针(为这个keypath注册观察者时提供的那个)。

上述字典的 NSKeyValueChangeKindKey 条目,提供有关发生的更改类型的信息。如果观察到的对象的值已经更改, 那么 NSKeyValueChangeKindKey 返回 NSKeyValueChangeSetting。根据注册观察者时指定的选项, 上述字典字典中的 NSKeyValueChangeOldKey 和 NSKeyValueChangeNewKey 项包含更改之前和之后的属性值。如果该属性是对象, 则直接提供该值。如果属性是标量值或 C语言结构, 则该值将被包装在 NSValue 对象中 (与键值对编码相同)。

如果观察到的属性是一对多关系, 则 NSKeyValueChangeKindKey 项还指明他们关系中的对象是插入、移除还是替换,通过分别返回 NSKeyValueChangeInsertion、NSKeyValueChangeRemoval 或NSKeyValueChangeReplacement来实现。
NSKeyValueChangeIndexesKey 的 “更改词典” 条目是一个 NSIndexSet 对象, 它指定了关系中已更改的索引。如果在注册观察者时将 NSKeyValueObservingOptionNew 或 NSKeyValueObservingOptionOld 指定为选项, 则更改字典中的 NSKeyValueChangeOldKey 和 NSKeyValueChangeNewKey 项是数组, 其中包含相关对象之前和之后的更改。

如果在注册观察者时指定了 NULL 作为上下文, 则将通知的keypath与要观察的keypath进行比较, 以确定更改了哪些内容。如果对所有已观察到的keypath使用了上下文, 则首先根据通知的上下文测试, 并查找匹配项, 使用keypath字符串比较来确定具体更改了哪些内容。如果为每个keypath提供了唯一的上下文, 一系列简单的指针比较会告诉您这个通知是否用于此观察者, 如果是, 则更改了什么keypath。

在任何情况下, 观察者应始终调用 observeValueForKeyPath:ofObject:change:context 的超类实现: 当它不识别上下文 (或在简单的情况下, 任何keypath) 时, 因为这意味着超类已注册通知。

删除一个作为观察者的对象

通过对观察到的对象发送 removeObserver:forKeyPath:context: 消息, 指定观察对象、keypath和context, 可以移除键值观察者。需要注意的是,观察者不会在自己dealloc时自动remove。会导致崩溃。观察者这个协议并不支持判断自己是不是观察者,所以,小心处理removeobserver的问题。

KVO规则 KVO Compliance

为了遵循KVO规则,类必须确保以下内容:

• 类属性必须符合键值对编程原则
• KVO 支持与 KVC 相同的数据类型
• 类将为属性发出 KVO 更改通知
• 相关key有正确的注册

手动更改通知提供了对何时发出通知的附加控制, 并需要额外的编码。通过实现类方法 automaticallyNotifiesObserversForKey, 可以控制子类的属性的自动通知:。

系统提供了自动发送通知的机制,当然,你也可手动设置通知(通过 willChangeValueForKey,didChangeValueForKey)

在很多情况下,一个属性的值取决于另一个对象中的一个或多个其他属性的值。如果一个属性的值发生变化,那么派生属性的值也应该被标记以进行更改。在一对一的关系中 通过重写 keyPathsForValuesAffectingValueForKey:方法实现。在一对多的关系中,以上方法不适用,需要具体问题具体分析。

实现原理 Key-Value Observing Implementation Details

基于 isa-swizzling技术,isa指针,顾名思义,只想一个对象的类(类维护了一个调度的表),这个调度的表包含指向该类实现的方法的指针IMP以及其他数据。

具体细节是:当您观察属性时,会创建一个私有子类,它实现了访问器方法和在更改属性值之前和之后调用适当的通知方法。然后使用称为“isa swizzling”的技术来改变观察对象的类别。

当一个观察者注册了一个对象的属性时,这个被注册对象的isa指针是被修改的,指向了一个中间类而不是真实的类。所以isa指针的值不一定反映实例的实际类。

你不应该依靠isa指针来确定类的成员资格。相反,您应该使用class方法来确定对象实例的类。

留下评论

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.