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

KVO官方文档

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

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

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中),确保正确的成对和有顺序的添加和移除消息,并且在观察者从内存中释放之前取消注册。
个人总结:观察者的注册和移除必须成对存在,并且要先注册后移除,需要开发者自身进行检查。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • 余观惟春景之盛貌兮,殊无物类之可仪比。芳草而满庭,欢愉而报春。溶溶而泄泄,春慵而酣酣。新霁而俱集,桃红而葱绿,盖矣...
    我是泛彼柏舟阅读 1,163评论 0 1
  • 我怎么了,昨天去见一位园长,她说她的教育理念是蒙氏,跟我一样啊!不知道为什么,一和别人聊我在乎的,聊擅长的,我反倒...
    8081f67fbe3d阅读 161评论 2 0
  • 五月的季节,还不能算炎夏,可是这天气却热的要命。搭乘着九路车,又开始了新一轮的义教。算起来,两年的义教也接近尾声了...
    姒柠樰阅读 323评论 0 1