[KVO翻译] KVO编程指南 Key-Value Observing Programming Guide

KVO编程指南 Key-Value Observing Programming Guide

1 Introduction to Key-Value Observing Programming Guide - KVO编程指南介绍

Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.

键 - 值观察是一种机制,当指定对象属性改变的时候允许另一个对象接受通知。

Important: In order to understand key-value observing, you must first understand key-value coding.

重要:了解键值观察之前,首先要理解键值编程 (key-value coding)

1.1 At a Glance - 概括

Key-value observing provides a mechanism that allows objects to be notified of changes to specific properties of other objects. It is particularly useful for communication between model and controller layers in an application. (In OS X, the controller layer binding technology relies heavily on key-value observing.) A controller object typically observes properties of model objects, and a view object observes properties of model objects through a controller. In addition, however, a model object may observe other model objects (usually to determine when a dependent value changes) or even itself (again to determine when a dependent value changes).

You can observe properties including simple attributes, to-one relationships, and to-many relationships. Observers of to-many relationships are informed of the type of change made—as well as which objects are involved in the change.

There are three steps to setting up an observer of a property. Understanding these three steps provides a clear illustration of how KVO works.

键 - 值观察是一种机制,当指定对象属性改变的时候允许另一个对象接受通知。在应用中,对于模型和控制器层之间的交流非常有用。(在 OS X 中,控制器层绑定技术严重依赖于键-值观察。)一个控制器对象通常观察模型对象的属性,视图对象通过控制器观察模型对象的属性。然后,一个模型对象可以观察其它模型对象(通常为了确定从属值是什么时候改变的),或者甚至观察自身(也是为了确定从属值何时变化)。

你可以观察一些属性,比如简单的属性,一对一关系的,一对多关系的。一对多关系的观察者可以收到变化的类型,以及哪些对象发生了改变。

建立属性的观察者有三步。下面提供一个清晰的KVO工作方式演示,来理解这三步。

1.First, see whether you have a scenario where key-value observing could be beneficial, for example, an object that needs to be notified when any changes are made to a specific property in another object.

1.首先,考虑这样的一个场景,例如当对一个A对象的特定做出任何改变的时候,B对象需要被通知。

image

2.The PersonObject must register as an observer of the BankObject’s accountBalance property by sending an addObserver:forKeyPath:options:context: message.

2.PersonObject必须注册为 BankObjectaccountBalance 属性的观察者,通过发送一个消息 addObserver:forKeyPath:options:context:

image

Note: The addObserver:forKeyPath:options:context: method establishes a connection between the instances of the objects that you specify. A connection is not established between the two classes, but rather between the two specified instances of the objects.

注意:addObserver:forKeyPath:options:context: 方法规定了一个指定对象实例之间的连接。注意不是两个类之间的连接,两个对象的实例。

3.In order to respond to change notifications, the observer must implement the observeValueForKeyPath:ofObject:change:context: method. This method implementation defines how the observer responds to change notifications. It is in this method that you can customize your response to a change in one of the observed properties.

3.为了响应变化的通知,观察者必须实现方法 observeValueForKeyPath:ofObject:change:context:。这个方法的实现中定义了观察者如何响应改变通知。可以在这个方法中定制被观察属性之一改变时的响应。

image

Registering for Key-Value Observing describes how to register and receive observation notifications.

Registering for Key-Value Observing说明如何注册和接受观察的通知。

4.The observeValueForKeyPath:ofObject:change:context: method is automatically invoked when the value of an observed property is changed in a KVO-compliant manner, or if a key upon which it depends is changed.

4.当被观察属性的值在KVO-compliant方式中改变 或者它依赖的一个key改变的时候,observeValueForKeyPath:ofObject:change:context: 方法自动被调用。

image

Registering Dependent Keys explains how to specify that the value of a key is dependent on the value of another key.

Registering Dependent Keys 解释了指定一个键的值依赖于另一个键的值。

KVO’s primary benefit is that you don’t have to implement your own scheme to send notifications every time a property changes. Its well-defined infrastructure has framework-level support that makes it easy to adopt—typically you do not have to add any code to your project. In addition, the infrastructure is already full-featured, which makes it easy to support multiple observers for a single property, as well as dependent values.

KVO Compliance describes the difference between automatic and manual key-value observing, and how to implement both.

Unlike notifications that use NSNotificationCenter, there is no central object that provides change notification for all observers. Instead, notifications are sent directly to the observing objects when changes are made. NSObject provides this base implementation of key-value observing, and you should rarely need to override these methods.

Key-Value Observing Implementation Details describes how key-value observing is implemented.

KVO的优点是,每次属性改变的时候,不需要自己实现发送通知。它良好定义的基础设施有架构层面的支持,使得它易于使用,通常不需要再工程中添加任何代码。此外,基础设施已经是全特性的,它可以很容易地支持单一属性的多个观察者,以及相关的值。

KVO Compliance 描述了自动和手动键值观察的区别,以及如何实现两者。

于使用 NSNotificationCenter 的通知不同,这里没有为所有观察者提供更改通知的中央对象,。相反,更改时通知直接被发送到观察对象。NSObject 提供了键值观察的基本实现,你应该很少需要重写这些方法。

Key-Value Observing Implementation Details描述键值观察室如何实现的。

2 Registering for Key-Value Observing - 注册键值观察

In order to receive key-value observing notifications for a property, three things are required:

  • The observed class must be key-value observing compliant for the property that you wish to observe.
  • You must register the observing object with the observed object, using the method addObserver:forKeyPath:options:context:.
  • The observing class must implement observeValueForKeyPath:ofObject:change:context:.

针对一个属性,为了接收键值观察通知,有3个要求:

  • 对于你希望观察的属性,被观察的类必须是键值观察兼容的。
  • 你必须对被观察的对象注册观察对象,使用方法 addObserver:forKeyPath:options:context:
  • 观察类必须实现方法 observeValueForKeyPath:ofObject:change:context:

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兼容的,在KVO Compliance中有描述。通常,如果它们被记录为这种苹果提供的框架属性,那就唯一KVO兼容的。

2.1 Registering as an Observer - 注册为一个观察者

In order to be notified of changes to a property, an observing object must first register with the object to be observed by sending it an addObserver:forKeyPath:options:context: message, passing the observer object and the key path of the property to be observed. The options parameter specifies the information that is provided to the observer when a change notification is sent. Using the option NSKeyValueObservingOptionOld specifies that the original object value is provided to the observer as an entry in the change dictionary. Specifying the NSKeyValueObservingOptionNew option provides the new value as an entry in the change dictionary. To receive both values, you would bitwise OR the option constants.

The example in Listing 1 demonstrates registering an inspector object for the property openingBalance.

为了属性改变时能被通知到,观察对象首先要对被观察对象进行注册,通过给被观察对象发送消息 addObserver:forKeyPath:options:context: 。传给的参数是观察者对象,被观察对象属性的键路径(key path)。可选的参数是当改变通知被发送的时候,提供给观察者的指定信息。使用选项 NSKeyValueObservingOptionOld 指定原始对象的值,在变化字典中提供给观察者。NSKeyValueObservingOptionNew 选项,提供新的值。为了收到这两个值,你应该对这两个选项常量使用位或。

清单1的例子演示给 openingBalance 属性注册一个观察者 inspector 对象。

- (void)registerAsObserver {
    /*
     注册 `inspector` ,来接收 `account` 对象的属性 `openingBalance` 
     改变时的通知,并且指定旧值和新值都应该提供给观察者。
     */
    [account addObserver:inspector
             forKeyPath:@"openingBalance"
                 options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
                    context:NULL];
}

When you register an object as an observer, you can also provide a context pointer. The context pointer is provided to the observer when observeValueForKeyPath:ofObject:change:context: is invoked. The context pointer can be a C pointer or an object reference. The context pointer can be used as a unique identifier to determine the change that is being observed, or to provide some other data to the observer.

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.

注册的时候,也可以提供一个上下文指针(context pointer),当 observeValueForKeyPath:ofObject:change:context: 被调用的时候,context pointer 会被提供给观察者。context pointer 可以是一个 C pointer 或者 一个对象引用。context pointer 可以用作唯一标识符,来确定正在被观察对象的变化,或者提供某些其他数据给观察者。

注意:键值观察方法 addObserver:forKeyPath:options:context: ,对于观察者对象,被观察的对象或者 context 都不会维持强引用。在必要的时候,你应该自己确保你对于它们维持强引用。

2.2 Receiving Notification of a Change - 针对改变接收通知

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.

The observer is provided the object and key path that triggered the observer notification, a dictionary containing details about the change, and the context pointer that was provided when the observer was registered.

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).

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.

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 NSKeyValueChangeOldKey and NSKeyValueChangeNewKey entries in the change dictionary are arrays containing the values of the related objects before, and after, the change.

The example in Listing 2 shows the observeValueForKeyPath:ofObject:change:context: implementation for an inspector that reflects the old and new values of the property openingBalance, as registered in Listing 1.

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

观察者被提供 触发观察者通知的对象和 key path ,就是一个字典,包含了变化和注册时填写的context pointer。

变化字典中 NSKeyValueChangeKindKey 提供了关于发生变化的信息。如果被观察对象的值被改变了,NSKeyValueChangeKindKey 返回 NSKeyValueChangeSetting 。根据观察者被注册时指定的选项,NSKeyValueChangeOldKey 和 NSKeyValueChangeNewKey ,在变化字典中包含了属性之前的值以及改变之后的值。如果属性是一个对象,值会被直接提供。如果属性是标量或者C结构体,值会被包含在一个 NSValue 对象中(比如 键值编程)。

如果被观察的属性是一对多的关系(比如数组,集合),NSKeyValueChangeKindKey 也会指定关系中的对象是否被返回的 NSKeyValueChangeInsertion 插入,NSKeyValueChangeRemoval 移除或者 NSKeyValueChangeReplacement 替代。

变化字典的条目 NSKeyValueChangeIndexesKey 是一个 NSIndexSet 对象,指出被改变关系的下标。如果注册的时候 NSKeyValueObservingOptionNew 或者 NSKeyValueObservingOptionOld 被指定为选项,变化字典中 NSKeyValueChangeOldKey 和 NSKeyValueChangeNewKey 就会是数组,包含了相关对象变化之前和之后的值。

清单2的例子演示了 observeValueForKeyPath:ofObject:change:context: 实现

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {

    if ([keyPath isEqual:@"openingBalance"]) {
        [openingBalanceInspectorField setObjectValue:
            [change objectForKey:NSKeyValueChangeNewKey]];
    }
    /*
     Be sure to call the superclass's implementation *if it implements it*.
     NSObject does not implement the method.
     */
    [super observeValueForKeyPath:keyPath
                         ofObject:object
                           change:change
                           context:context];
}

2.3 Removing an Object as an Observer 移除对象观察者

You remove a key-value observer by sending the observed object a removeObserver:forKeyPath: message, specifying the observing object and the key path. The example in Listing 3 removes the inspector as an observer of openingBalance.

你可以给被观察对象发送一个消息 removeObserver:forKeyPath: 来移除键值观察,指定观察者对象和 key path 。清单3 的例子移除了 openingBalance 的观察者 inspector 。

- (void)unregisterForChangeNotification {
    [observedObject removeObserver:inspector forKeyPath:@"openingBalance"];
}

If the context is an object, you must keep a strong reference to it until removing the observer. After receiving a removeObserver:forKeyPath: message, the observing object will no longer receive any observeValueForKeyPath:ofObject:change:context: messages for the specified key path and object.

如果 context 是一个对象,在移除观察者之前你必须对它保持一个强引用。接收到 removeObserver:forKeyPath: 消息后,观察对象将不在接收任何指定 key path 和对象的 observeValueForKeyPath:ofObject:change:context: 消息。

3 KVO Compliance - KVO 兼容性

In order to be considered KVO-compliant for a specific property, a class must ensure the following:

  • The class must be key-value coding compliant for the property, as specified in Ensuring KVC Compliance. KVO supports the same data types as KVC.
  • The class emits KVO change notifications for the property.
  • Dependent keys are registered appropriately (see Registering Dependent Keys).

There are two techniques for ensuring the change notifications are emitted. Automatic support is provided by NSObject and is by default available for all properties of a class that are key-value coding compliant. Typically, if you follow standard Cocoa coding and naming conventions, you can use automatic change notifications—you don’t have to write any additional code.

Manual change notification provides additional control over when notifications are emitted, and requires additional coding. You can control automatic notifications for properties of your subclass by implementing the class method automaticallyNotifiesObserversForKey:.

考虑到指定属性的 KVO-compliant,类必须确认下面几点:

  • 类对于属性必须被键值编程兼容,在 Ensuring KVC Compliance 中有指出。KVO支持与KVC相同的数据类型。
  • 类对于属性能发出KVO改变通知。
  • 在注册时适当的设置相关的 key (见 Registering Dependent Keys

有两种技术确保改变通知被发出。自动支持由 NSObject 提供,并且对于一个类的所有属性键值编程兼容性都是可用的。通常来说,如果遵循标准的Cocoa 编程和命名规范,你就可以使用自动变化通知,不用写任何额外的代码。

手动变化通知在当通知被发出的时候提供了额外的控制,并且需要添加一些代码。你可以通过实现类方法 automaticallyNotifiesObserversForKey: 来控制你子类属性的自动通知。

3.1 Automatic Change Notification - 自动变化通知

NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods. Automatic notification is also supported by the collection proxy objects returned by, for example, mutableArrayValueForKey:.

The examples shown in Listing 1 result in any observers of the property name to be notified of the change.

Listing 1 Examples of method calls that cause KVO change notifications to be emitted

NSObject 提供了一个自动键值变化通知的基本实现。自动键值变化通知告知所做改变的观察者使用键值兼容的 accessors,以及键值编程方法。自动通知也被返回的集合代理对象支持,例如 mutableArrayValueForKey:

清单1的例子展示了改变时,属性名被修改的情况下任何观察者的结果。

清单1,导致发出KVO变化通知的方法调用例子

// Call the accessor method.
[account setName:@"Savings"];

// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];

// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];

// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];

3.2 Manual Change Notification - 手动变化通知

Manual change notification provides more granular control over how and when notifications are sent to observers. This can be useful to help minimize triggering notifications that are unnecessary, or to group a number of changes into a single notification.

A class that implements manual notification must override the NSObject implementation of automaticallyNotifiesObserversForKey:. It is possible to use both automatic and manual observer notifications in the same class. For properties that perform manual notification, the subclass implementation of automaticallyNotifiesObserversForKey: should return NO. A subclass implementation should invoke super for any unrecognized keys. The example in Listing 2 enables manual notification for the openingBalance property allowing the superclass to determine the notification for all other keys.

Listing 2 Example implementation of automaticallyNotifiesObserversForKey:

手动变化通知在当通知被发送给观察者的时候提供了更多精细的控制。对于减少不必要的触发通知,或者一大波变化给一个通知,都很有帮助。

实现手动通知的类必须重写 NSObject 实现的方法 automaticallyNotifiesObserversForKey: 。有可能在相同的类中使用自动和手动的观察通知。对于执行手动通知的属性,子类的 automaticallyNotifiesObserversForKey: 方法实现应该返回 NO。子类实现中对于任何为确认的 key,应该调用父类。清单2的例子对于 openingBalance 属性启用了手动通知,允许父类来决定所有其他 key 的通知。

清单2 automaticallyNotifiesObserversForKey:实现的例子

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {

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

To implement manual observer notification, you invoke willChangeValueForKey: before changing the value, and didChangeValueForKey: after changing the value. The example in Listing 3 implements manual notifications for the openingBalance property.

Listing 3 Example accessor method implementing manual notification

要实现手动观察通知,在值变化之前要调用 willChangeValueForKey:,值变化之后要调用 didChangeValueForKey: 。清单3的例子,对于 openingBalance 属性实现了手动通知。

清单3 accessor 方法实现手动通知的例子

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

You can minimize sending unnecessary notifications by first checking if the value has changed. The example in Listing 4 tests the value of openingBalance and only provides the notification if it has changed.

Listing 4 Testing the value for change before providing notification

你可以先检查值是否改变,来将不必要发送的通知最小化。清单3的例子测试了 openingBalance 的值,并且只提供了如果它改变的通知。

清单4 在通知之前测试值得变化

- (void)setOpeningBalance:(double)theBalance {
    if (theBalance != _openingBalance) {
        [self willChangeValueForKey:@"openingBalance"];
        _openingBalance = theBalance;
        [self didChangeValueForKey:@"openingBalance"];
    }
}

If a single operation causes multiple keys to change you must nest the change notifications as shown in Listing 5.

Listing 5 Nesting change notifications for multiple keys

如果一个操作导致多个 key 的变化,你必须像清单5 一样将变化通知进行嵌套。

清单5 对多个 key 将变化通知嵌套

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

In the case of an ordered to-many relationship, you must specify not only the key that changed, but also the type of change and the indexes of the objects involved. The type of change is an NSKeyValueChange that specifies NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, or NSKeyValueChangeReplacement. The indexes of the affected objects are passed as an NSIndexSet object.

The code fragment in Listing 6 demonstrates how to wrap a deletion of objects in the to-many relationship transactions.

Listing 6 Implementation of manual observer notification in a to-many relationship

在有序,一对多关系的情况下,你必须指出不仅 key 发生了变化,还要指出变化的类型以及被调用对象的下标。变换的类型是 NSKeyValueChange ,可以指定 NSKeyValueChangeInsertionNSKeyValueChangeRemoval或者 NSKeyValueChangeReplacement 。受影响对象的索引作为 NSIndexSet 对象被传递。

清单6 一对多关系的手动观察通知的实现

- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];

    // Remove the transaction objects at the specified indexes.

    [self didChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
}

4 Registering Dependent Keys - 注册相关 key

There are many situations in which the value of one property depends on that of one or more other attributes in another object. If the value of one attribute changes, then the value of the derived property should also be flagged for change. How you ensure that key-value observing notifications are posted for these dependent properties depends on the cardinality of the relationship.

一个属性的值取决于一个或者多个其他对象的属性,有很多种情况。如果一个属性变化,那么派生属性也应该被标记改变。对于这些相关属性依赖于关系的基数,你如果确保通知被发出。

4.1 To-one Relationships - 一对一关系

To trigger notifications automatically for a to-one relationship you should either override keyPathsForValuesAffectingValueForKey: or implement a suitable method that follows the pattern it defines for registering dependent keys.

For example, the full name of a person is dependent on both the first and last names. A method that returns the full name could be written as follows:

对于一对一关系,为了自动触发通知,你应该重写 keyPathsForValuesAffectingValueForKey: 方法,或者实现一个合适的方法,遵循一种模式,它定义取决于注册相关的key。

例如,一个人的全名依赖于人的姓和名。一个返回全名的方法可以写成下面形式:

- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}

An application observing the fullName property must be notified when either the firstName or lastName properties change, as they affect the value of the property.

One solution is to override keyPathsForValuesAffectingValueForKey: specifying that the fullName property of a person is dependent on the lastName and firstName properties. Listing 1 shows an example implementation of such a dependency:

Listing 1 Example implementation of keyPathsForValuesAffectingValueForKey:

应用观察 fullName 属性,必须注意到当 firstName 或者 lastName 属性变化时,会影响到 fullName属性的值。

一种解决方法是重写 keyPathsForValuesAffectingValueForKey: ,指定人的 fullName 属性依赖于 lastName 和 firstName 属性。清单1 展示了这样一个例子:

清单1 keyPathsForValuesAffectingValueForKey: 的实现例子

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {

    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

Your override should typically invoke super and return a set that includes any members in the set that result from doing that (so as not to interfere with overrides of this method in superclasses).

You can also achieve the same result by implementing a class method that follows the naming convention keyPathsForValuesAffecting, where is the name of the attribute (first letter capitalized) that is dependent on the values. Using this pattern the code in Listing 1 could be rewritten as a class method named keyPathsForValuesAffectingFullName as shown in Listing 2.

Listing 2 Example implementation of the keyPathsForValuesAffecting naming convention

重写,通常应该调用 super ,并且返回一个 set ,包括这样做的导致结果的任何成员。(在父类中不要因为这个方法的重写造成干扰)。

你也可以通过实现一个类方法达到同样的结果,遵循命名约定 keyPathsForValuesAffecting, 是属性名(首字母大写),依赖于值。清单1中使用这种模式的代码,可以像在清单2中被重写为一个类方法 keyPathsForValuesAffectingFullName

清单2 keyPathsForValuesAffecting 命名约定的实现例子

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

You can’t override the keyPathsForValuesAffectingValueForKey: method when you add a computed property to an existing class using a category, because you’re not supposed to override methods in categories. In that case, implement a matching keyPathsForValuesAffecting class method to take advantage of this mechanism.

Note: You cannot set up dependencies on to-many relationships by implementing keyPathsForValuesAffectingValueForKey:. Instead, you must observe the appropriate attribute of each of the objects in the to-many collection and respond to changes in their values by updating the dependent key yourself. The following section shows a strategy for dealing with this situation.

当你使用一个 category 给一个现有的类添加一个计算属性的时候,你不能重写 keyPathsForValuesAffectingValueForKey: 方法,因为不允许重写分类(categories)中的方法。这种情况下,实现一个匹配的 keyPathsForValuesAffecting<Key> 类方法,体现了这种机制的优势。

注意:你不能通过实现 keyPathsForValuesAffectingValueForKey: 方法建立依赖于一对多的关系。相反,你必须观察一对多集合中对象的每个响应属性,并通过更新自己依赖的 key 来响应它们值的变化。下面一节讲了处理这种情况的一种策略。

4.2 To-many Relationships - 一对多关系

The keyPathsForValuesAffectingValueForKey: method does not support key-paths that include a to-many relationship. For example, suppose you have a Department object with a to-many relationship (employees) to a Employee, and Employee has a salary attribute. You might want the Department object have a totalSalary attribute that is dependent upon the salaries of all the Employees in the relationship. You can not do this with, for example, keyPathsForValuesAffectingTotalSalary and returning employees.salary as a key.

There are two possible solutions in both situations:

1.You can use key-value observing to register the parent (in this example, Department) as an observer of the relevant attribute of all the children (Employees in this example). You must add and remove the parent as an observer as child objects are added to and removed from the relationship (see Registering for Key-Value Observing). In the observeValueForKeyPath:ofObject:change:context: method you update the dependent value in response to changes, as illustrated in the following code fragment:

keyPathsForValuesAffectingValueForKey: 方法不支持包含一对多关系的 key-path。例如,Department 对象有一个一对多的关系 对于 Employee,Employee 有 salary 属性。你希望 Department 对象有一个 totalSalary 属性,这个属性依赖于所有 Employees。你不能像下面这样,keyPathsForValuesAffectingTotalSalary 并且将 employees.salary 作为一个 key 返回。

在两种情况中有两个可能的解决方法:

1.可以使用键值观察注册 parent(例子中就是 Department) 作为所有 children(Employees) 相关属性的观察者。你必须添加和移除 parent 。在 observeValueForKeyPath:ofObject:change:context: 方法中,在响应变化中更新相关的值,就像下面的代码所示:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (context == totalSalaryContext) {
        [self updateTotalSalary];
    }
    else
    // deal with other observations and/or invoke super...
}

- (void)updateTotalSalary {
    [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}

- (void)setTotalSalary:(NSNumber *)newTotalSalary {

    if (totalSalary != newTotalSalary) {
        [self willChangeValueForKey:@"totalSalary"];
        _totalSalary = newTotalSalary;
        [self didChangeValueForKey:@"totalSalary"];
    }
}

- (NSNumber *)totalSalary {
    return _totalSalary;
}

2.If you’re using Core Data, you can register the parent with the application’s notification center as an observer of its managed object context. The parent should respond to relevant change notifications posted by the children in a manner similar to that for key-value observing.

2.如果使用Core Data,你可以在应用程序的通知中心注册 parent 作为它管理的对象 context 的观察者。parent 应该响应 与键值观察类似方式被 children 发出的的变化通知。

4.3 Key-Value Observing Implementation Details - 键值观察实现细节

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

自动键值观察是使用一种称为 isa-swizzling 的技术实现。

isa 指针,顾名思义,只想一个对象的类,维持一个调度表。这个调度表基本上包含指向类的方法实现以及其他数据的指针。

当一个观察者被注册,被观察者对象的 isa 指针所指的对象的属性被修改,指的是一个中间的类而不是真正的类。结果就是 isa 指针的值并不一定反映实际类的实例。

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

苹果官方文档地址:Key-Value Observing Programming Guide

由于笔者水平有限,文中如果有错误的地方,或者有更好的方法,还望大神指出。
附上本文的所有 demo 下载链接,【GitHub】
如果你看完后觉得对你有所帮助,还望在 GitHub 上点个 star。赠人玫瑰,手有余香。

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

推荐阅读更多精彩内容