Chapter     18

Key-Value Programming

Here you are, at the start of the final chapter of this book. Congratulations on the great job you’ve done throughout this journey! The advanced topic for this chapter is key-value programming, a set of language mechanisms and APIs that can enable you to both simplify program design and also implement more extensible and reusable code. The Objective-C key-value programming features are collectively referred to as key-value coding (KVC) and key-value observing (KVO). Key-value coding enables your code to access and manipulate an object’s properties indirectly, by name (i.e., key), rather than through accessor methods or backing instance variables. Key-value observing enables objects to be notified of changes to the properties of other objects. In this chapter, you will learn the fundamentals of these technologies, key implementation details, and how to use them in your programs.

Key-Value Coding

As stated earlier, key-value coding is a mechanism for accessing an object’s properties. In Chapter 2 of this book, you learned that properties encapsulate an object’s internal state. This state is not accessed directly, but rather using properties’ accessor methods (also known as getter and setter methods). The compiler can generate these methods automatically (according to the property declaration). This reduces the amount of code to write and also facilitates program consistency and maintainability. In the following code fragment, a class named Hello declares a property named greeting of type NSString.

@interface Hello : NSObject
@property NSString *greeting;
...
@end

This property can be accessed using either standard property accessors or dot notation syntax (recall that the compiler transforms property dot notation expressions into their corresponding accessor method invocations). Given a Hello instance named helloObject, the value for the greeting property can be retrieved using either of the following expressions:

[helloObject greeting]
helloObject.greeting

Conversely, the value for the greeting property can be set using either of the following expressions:

[helloObject setGreeting:newValue]
helloObject.greeting = newValue

Recall that a property's backing instance variable can also be accessed directly if the object associated with the property is not fully constructed (i.e., if the variable is accessed within a class init or dealloc method). The init method for the Hello class accesses the backing instance variable of the greeting property in Listing 18-1.

Listing 18-1.  Accessing a Property’s Backing Instance Variable

@implementation Hello
- (id)init
{
  if ((self = [super init]))
  {
    _greeting = @"Hello";
...
}

Note that these mechanisms for accessing a property are compile-time expressions, and hence tightly couple the calling code to the receiver object. With that background, you can now examine key-value coding. In a nutshell, it provides a key-value pair mechanism for accessing a property, where the key is the property’s name and the value is the property value. Now this mechanism should already be familiar to you, as it is identical to that used to access entries in a dictionary (e.g., an NSDictionary instance). For example, given a Hello instance named helloObject, the following expression uses key-value coding APIs to retrieve the value of the greeting property.

[helloObject valueForKey:@"greeting"]

Observe that the key provided is the name of the property, in this case greeting. Key-value coding APIs can also be used to set the value for the greeting property.

[helloObject setValue:@"Hello" forKey:@"greeting"]

OK, so this is all fine and good, but you may still be wondering, “What’s the advantage of using KVC to access properties versus the standard property accessor methods?” Well to begin with, key-value coding allows you to access a property using a string that can be varied at runtime, and thus provides a more dynamic, flexible approach for accessing and manipulating an object’s state. The following are several of the key benefits to key-value coding:

  • Configuration-based property access. KVC enables your code to access properties using a parameter-driven, common API.
  • Loose coupling. Use of KVC for property access reduces coupling between software components, thereby improving software maintainability.
  • Simplifies code. KVC can reduce the amount of code that you have to write, particularly in scenarios where you need to access a specific property based on a variable. Instead of using conditional expressions to determine the correct property accessor method to invoke, you can employ one KVC expression using the variable as its parameter.

Let’s say that you need to dynamically update the state of a model based on an input from the user. Listing 18-2 provides an implementation of this logic using standard property accessors.

Listing 18-2.  Updating Model State Using Standard Property Accessors

- (void)updateModel:(NSString *)value forState:(NSString *)state
{
  if ([state isEqualToString:@"species"])
  {
    [self setSpecies:value];
  }
  else if ([state isEqualToString:@"genus"])
  {
    [self setGenus:value];
  }
  ...
}

As shown in Listing 18-2, this method uses conditional expressions and standard property accessors to update the value of the correct property. Although functional, this implementation doesn’t scale well and is difficult to maintain because the code must be updated whenever the property names and/or number of properties changes. Contrast that with Listing 18-3, which implements the same logic using key-value coding.

Listing 18-3.  Updating Model State Using Key-Value Coding

- (void)updateModel:(NSString *)value forState:(NSString *)state
{
  [self setValue:value forKey:state];
}

Comparing the two listings, it should be pretty obvious to you that the KVC-based implementation is a vast simplification over the earlier approach. As shown in Listing 18-3, KVC can greatly reduce the amount of code that you have to write to implement this type of logic. Compared to Listing 18-2, it is also easier to both maintain and evolve because changing the properties of the model does not require any updates to the method (shown in Listing 18-3) that uses KVC.

Keys and Key Paths

Key-value coding uses keys and key paths for property access. A key is a string that identifies a specific property. A key path specifies a sequence of object properties to traverse. The KVC APIs support accessing both individual and multiple properties of an object. The properties can be object types, basic types, or C structs; basic types and structs are automatically wrapped to/from corresponding object types.

Just as dot notation syntax enables you to traverse nested properties in an object graph, KVC provides this capability through key paths. As an example, imagine a Person class that declares a property named name of type Name, and the Name class declares a property named firstName of type NSString (see Figure 18-1).

9781430250500_Fig18-01.jpg

Figure 18-1. Person object graph

The object-oriented principle of composition is used here to create an object graph comprised of a Person object that has a Name instance and an Address instance. Given this object graph, for a Person instance named person, dot notation syntax could be used to retrieve the value of the firstName property, and then set the value of the firstName property as follows:

person.name.firstName = @"Bob";

The key-value coding mechanism includes an API that provides the same functionality (i.e., the setValue:forKeyPath: method). Its arguments include a key path that also uses dot notation.

[person setValue:@"Bob" forKeyPath:@"name.firstName"];

The valueForKeyPath: API would be used to retrieve a property value specified by a key path, for example

NSString *name = [person valueForKeyPath:@"name.firstName"];

As indicated earlier, key-value coding includes APIs for accessing multiple properties of an object. In the object graph depicted in Figure 18-1, a Person class declares a Name property and an Address property. Key-value coding can be used to retrieve the keys and corresponding values for an input collection of keys. Listing 18-4 uses the KVC dictionaryWithValuesForKeys: method to retrieve the keys and values for the name and address properties of a Person instance named person.

Listing 18-4.  Using KVC to Retrieve Multiple Property Values

NSArray *personKeys = @[@"name", @"address"];
NSDictionary *personValues = [person dictionaryWithValuesForKeys:personKeys];

Conversely, Listing 18-5 uses the KVC setValuesForKeysWithDictionary: method to set values for the name and address properties of a Person instance named person.

Listing 18-5.  Using KVC to Set Multiple Property Values

Name *tom = [Name new];
Address *home = [Address new];
NSDictionary *personProperties = @{@"name":tom, @"address":home};
[person setValuesForKeysWithDictionary:personProperties];

These are just some of the features that key-value coding provides. In the following paragraphs, you will examine this technology in more detail and learn some of its more advanced features.

KVC Design and Implementation

The design of key-value coding is based on the following two fundamental constructs:

  • A mechanism that can be used to access the properties of an object indirectly by name (or key), rather than through direct invocation of accessor methods.
  • A mechanism for mapping keys to the appropriate property accessor methods and/or property backing variables.

The NSKeyValueCoding informal protocol defines the mechanism for accessing objects indirectly by name/key. This protocol declares the key-value coding API, consisting of class and instance methods along with constant values. The methods enable you to get and set property values, perform property validation, and change the default behavior of the key-value coding methods for getting property values. Earlier in this chapter, you used several of these APIs (e.g., valueForKey:, selectValue:forKey:, etc.). NSObject provides the default implementation of the NSKeyValueCoding protocol, and thus any class that descends from NSObject has built-in support for key-value coding.

Key-Value Coding APIs

The NSKeyValueCoding informal protocol defines the standard key-value coding APIs. These methods enable you to get/set property values, perform key-value coding based property validation, and configure key-value coding operation. Depending upon the select method, its input parameters are a key, key path, value, and (for the validation APIs) an error object pointer. In the previous sections, you used several of the methods to get and set property values; now I’ll provide an overview of a few other methods specified by this protocol.

The NSKeyValueCoding accessInstanceVariablesDirectly class method enables your class to control whether the key-value coding mechanism should access a property’s backing variable directly, if no accessor method (for the property) is found. The method returns a Boolean value. YES indicates that the key-value coding methods should access the corresponding instance variable directly, and NO indicates that they should not. The default (NSObject) implementation returns YES; your class should override this method to control this behavior.

Because key-value coding uses a key value (determined at runtime) to access a property, it also provides APIs to handle scenarios where the input key does not correspond to an object’s properties. The NSKeyValueCoding valueForUndefinedKey: and setValue:forUndefinedKey: methods control how key-value coding responds to undefined key scenarios. Specifically, one of these methods is invoked when the valueForKey: or setValue:forKey: methods find no property corresponding to the input key. The default implementation of the undefined key methods raises an NSUndefinedKeyException, but subclasses can override these methods to return a custom value for undefined keys. Listing 18-6 overrides the valueForUndefinedKey: method of the Hello class implementation shown earlier (see Listing 18-1) to return the value of the property named greeting if the input key is "hi".

Listing 18-6.  Hello Class Implementation of the valueForUndefinedKey: Method

@implementation Hello
...
- (id)valueForUndefinedKey:(NSString *)key
{
  if ((nil != key) && ([@"hi" isEqualToString:key]))
  {
    return self.greeting;
  }
  [NSException raise:NSUndefinedKeyException
              format:@"Key %@ not defined", key];
  return nil;
}
...
@end

The protocol defines two methods for validating a property value: validateValue:forKey:error: and validateValue:forKeyPath:error:. You’ll examine these methods later in this chapter.

For collections, the NSKeyValueCoding protocol includes methods that can return a mutable instance of a collection (array or set) for a given key. The following code fragment uses the mutableArrayValueForKey: method to return a mutable array from an object named order for the key "items".

NSMutableArray *items = [order mutableArrayValueForKey:@"items"];

Note that these APIs return mutable collection instances, even if the property is a read-only collection.

Key-Value Search Patterns

So, when a KVC API is invoked, how does it get/set property values, given that its input parameters are a key/key path (along with a value for set methods)? KVC accomplishes this through use of a set of search patterns that rely on property accessor method naming conventions.

These search patterns attempt to get/set property values using their corresponding accessor methods, only resorting to directly accessing the backing instance variable if no matching accessor method is found. The NSObject default implementation of KVC includes several search patterns to support the various KVC getter/setter methods. The search pattern for the setValue:forKey: method is as follows:

  1. KVC searches the target (i.e., receiver) class for an accessor method whose name matches the pattern set<Key>:, where <Key> is the name of the property. Hence, if your object invokes the setValue:forKey: method, providing a value of "name" for the key, KVC would search the class for an accessor method named setName:.
  2. If no accessor is found, and the receiver’s class method accessInstanceVariablesDirectly returns YES, then the receiver class is searched for an instance variable whose name matches the pattern _<key>, _is<Key>, <key>, or is <Key>. In this case, KVC would search for an instance variable named _name, _isName, name, or isName.
  3. If a matching accessor method or instance variable is found, it is used to set the value. If necessary, the value is wrapped.
  4. If no appropriate accessor or instance variable is found, the receiver’s setValue:forUndefinedKey: method is invoked.

The search patterns support various categories of properties (attributes, object types, collections). As mentioned previously, your classes can also override the valueForUndefinedKey: and setValue:forUndefinedKey: methods to handle cases where a property can’t be found for a given key.

Note   Key-value coding can be used to access three different categories of properties: simple attributes, to-one relationships, or to-many relationships.  An attribute is a property that is a primitive (i.e., basic type), Boolean, string (NSString), or value object (NSDate, NSNumber, NSDecimalNumber, NSValue). A property that conforms to a to-one relationship is an object type and (potentially) has properties of its own. This includes framework classes (e.g., Foundation Framework) and your own custom classes. A property that conforms to a to-many relationship consists of a collection of similar type objects. These are most commonly collection types (NSArray, NSSet, etc.).

Property Accessor Naming Conventions

As you learned in the last section, the default NSObject key-value coding implementation relies on key-value compliant properties. Specifically, such properties must comply with the standard Cocoa naming conventions for property accessor methods. These can be summarized as follows:

  • A getter method should use the <key> accessor form, where the method and property name are identical. For a property named firstName, the getter method should also be named firstName. This is true for all data types except for Boolean properties, for which the getter method should be prepended with the word is (e.g., isFirstName in the previous example).
  • The setter method for a property should use the set<key> form, where the method name is the same as the property, prepended with the word set. For a property named firstName, the setter method should be named setFirstName:.

These conventions should be used for attribute and to-one relationship properties. As an example, given a property named person of type Type, the getter and setter accessor methods for the property should be named as follows:

- (Type)person;
- (void)setPerson:(Type)newValue;

In the preceding example, if the type of the property person is Boolean, the getter method could instead be named as follows:

- (BOOL)isPerson;

As another example, a property of type NSString * named address would have accessors with the following names:

- (NSString *)address
- (void)setAddress:(NSString *)

Note that if your properties are defined by autosynthesis (as explained in Chapter 2), the compiler automatically generates accessor methods that follow this convention.

Key-value coding depends on these naming conventions; hence, your code must follow these in order for key-value coding to work properly.

To-Many Property Accessor Naming Conventions

In addition to the <key> and set<key> accessor forms, to-many relationship properties (i.e., collections) should implement an additional set of accessor methods. These additional methods can improve the performance of KVC mutable collection APIs, enable you to model to-many relationships with classes beyond NSArray or NSSet (i.e., your own custom classes), and enable your code to use the KVC accessor methods to make modifications to collections.

To-many relationship properties have several accessor patterns, depending upon the type of collection. A class with properties that maintain an ordered, to-many relationship (these are typically of type NSArray or NSMutableArray) should implement the methods listed in Table 18-1.

Table 18-1. Methods to Implement for Ordered, To-Many Relationship Properties

Method Ordered, To-Many
Relationship
Mutable Ordered, To-Many
Relationship
countOf<Key> Required Required
objectIn<Key>AtIndex: (or <key>AtIndexes:) Required Required
get<Key>:range: Optional Optional
insertObject:in<Key>AtIndex: (or insert<Key>:atIndexes:) Required
removeObjectFrom<Key>AtIndex: (or remove<Key>AtIndexes:) Required
replaceObjectIn<Key>AtIndex:withObject: (or replaceKeyAtIndexes:with<Key>:) Optional

Implementing the optional methods of Table 18-1 can improve performance when accessing collection elements. Recall that Figure 18-1 depicts the object graph for a Person class. Imagine a to-many property named people (of type NSArray *). Listing 18-7 provides implementations of the countOf<Key> and objectIn<Key>AtIndex: methods for this property.

Listing 18-7.  Example Implementations of Accessors for an Ordered, To-Many Property

- (NSUInteger)countOfPeople
{
  return [self.people count];
}

- (Person *)objectInPeopleAtIndex:(NSUInteger)index
{
  return [self.people objectAtIndex:index];
}

A class with properties that maintain an unordered, to-many relationship (typically of type NSSet or NSMutableSet) should implement the methods listed in Table 18-2.

Table 18-2. Methods to Implement for Unordered, To-many Relationship Properties

Method Ordered, To-Many
Relationship
Mutable Ordered, To-Many
Relationship
countOf<Key> Required Required
enumeratorOf<Key> Required Required
memberOf<Key>: Optional Optional
add<Key>Object: (or add<Key>:) Required
remove<Key>Object: (or remove<Key>:) Required

Imagine that the to-many property named people is of type NSSet * (i.e., an unordered, to-many relationship property). Listing 18-8 provides implementations of the enumeratorOf<Key> and memberOf<Key> methods for this property.

Listing 18-8.  Example Implementations of Accessors for an Unordered, To-Many Property

- (NSEnumerator *)enumeratorOfPeople
{
  return [self.people objectEnumerator];
}

- (Person *)memberOfPeople:(Person *)obj
{
  return [self.people member:obj];
}

Key-Value Validation

Key-value coding also provides infrastructure for validating property values. The KVC validation mechanism comprises a set of APIs and a standard convention for custom property validation methods. The KVC validation methods can be called directly, or indirectly by invoking the KVC validateValue:forKey:error: method.

Key-value validation specifies a convention for naming a property’s validation method. The selector for a validation method is

validate<Key>:error:

<Key> is the name of the property. For example, given a property named person, its validation method is validatePerson:error:. Listing 18-9 provides an example implementation of the validation method for the city property of the Address class shown in Figure 18-1.

Listing 18-9.  Example Validation Method Implementation for the City Property

- (BOOL)validateCity:(id *)value error:(NSError * __autoreleasing *)error
{
  if (*value == nil)
  {
    if (error != NULL)
    {
      *error = [NSError errorWithDomain:@"Invalid Property Value (nil)"
                                   code:1
                               userInfo:nil];
    }
    return NO;
  }
  return YES;
}

As shown in the preceding method implementation, the method returns a Boolean value of YES or NO. It returns NO if the object value is not valid and a valid value cannot be created and returned. In this case, an NSError object is also returned (via double-indirection) that indicates the reason that validation failed. A validation method returns YES under the following scenarios:

  • If the input object value is valid, YES is returned.
  • If the input object value is not valid, but a new object value that is valid is created and returned, then YES is returned after setting the input value to the new, valid value.

The KVC method that performs property validation (by invoking the corresponding validate<Key>:error: method) is declared as follows:

- (BOOL)validate<Key>:(id *)value error:(NSError **)error;

Note that key-value coding does not automatically invoke a property validation method. Your code must manually perform property validation, typically by calling its validation method directly or using the KVC validation API. Listing 18-10 illustrates an example usage of key-value validation on an Address instance named address to validate a value before setting it on the Address property named city.

Listing 18-10.  Using Key-Value Validation

NSError *error;
NSString *value = @"San Jose";
BOOL result = [address validateValue:&value forKey:@"city" error:&error];
if (result)
{
  [address setValue:value forKey:@"city"];
}

Key-Value Coding Collection Operators

Key-value coding includes a set of operators that allow actions to be performed on the items of a collection using key path dot notation. The format of the specialized key path for the KVC collection operators is

[email protected]

This specialized key path is used as a parameter of the valueForKeyPath: method to perform collection operations. Note that components of this key path are separated by a period. The collectionKeyPath, if provided, is the key path of the array or set (relative to the receiver) on which the operation is performed. The operator (preceded by an ampersand) is the operation performed on the collection. Finally, the propertyKeyPath is the key path for the property of the collection that the operator uses. There are several types of collection operators: simple collection operators that operate on the properties of a collection, object operators that provide results when the operator is applied on a single collection instance, and collection operators that operate on nested collections and return an array or set depending on the operator.

The best way to illustrate how the collection operators work is by example, so that’s what you’ll do here. Listing 18-11 declares the interface to an OrderItem class that includes several properties.

Listing 18-11.  OrderItem Class Interface

@interface OrderItem : NSObject

@property NSString *description;
@property NSUInteger *quantity;
@property float *price;

@end

Given a collection of OrderItem instances stored in an NSArray object named orderItems, the following expression uses the KVC valueForKeyPath: expression with the @sum collection operator to sum the prices of the items.

NSNumber *totalPrice = [orderItems valueForKeyPath:@"@sum.price"];

The number of objects in the orderItems collection can be determined using the @count collection operator, as follows:

NSNumber *totalItems = [orderItems valueForKeyPath:@"@count"];

The number of distinct order items, each with a different description, can be obtained by using the @distinctUnionOfObjects operator, as follows:

NSArray *itemTypes = [orderItems
                      valueForKeyPath:@"@distinctUnionOfObjects.description"];

Now let’s look at an example that uses a collection operator on a collection property. Listing 18-12 declares the interface to an Order class with a single property.

Listing 18-12.  Order Class Interface

@interface Order : NSObject

@property NSArray *items;

@end

Given an Order instance named order, the @count operator can be used to determine the number of objects in the key path collection. In this case, the key path consists of the key path to the collection (items), followed by the @count operator.

NSNumber *totalItems = [order valueForKeyPath:@"items.@count"];

The preceding examples demonstrate usage of a subset of the available KVC collection operators. The Apple Key-Value Programming Guide provides a complete reference of the collection operators.

Key-Value Observing

Key-value observing (KVO) is a mechanism that enables objects to be notified of changes to the properties of other objects. It is essentially an implementation of the observer software design pattern, and is implemented in numerous software libraries and frameworks. In fact, it is a key component of the model-view-controller (MVC) design pattern, a central component of the Cocoa and Cocoa Touch frameworks. Key-value observing provides numerous benefits, including the decoupling of observer objects from the object being observed (i.e., the subject), framework-level support, and a full-featured set of APIs. The following example uses key-value observing to register an Administrator object as an observer of the name property of a Person instance named person.

Administrator *admin = [Administrator new];
[person addObserver:admin
         forKeyPath:@"name"
            options:NSKeyValueObservingOptionNew
            context:NULL];

Invoking the addObserver:forKeyPath:options:context: method creates a connection between the subject and the observer object. When the value of the observed property changes, the subject invokes the observer’s observeValueForKeyPath:ofObject:change:context: method. In this method, an observer class implements logic to handle the property change. A subject automatically invokes this method when the value of an observed property is changed in a KVO-compliant manner, or the key on which it depends is changed. Observers must implement this method for key-value observing to work properly. Listing 18-13 provides an example implementation of this method for the Administrator class.

Listing 18-13.  Example Implementation of observeValueForKeyPath:ofObject:change:context: Method

@implementation Administrator

...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
        change:(NSDictionary *)change context:(void *)context
{
  if ([@"name" isEqual:keyPath])
  {
    // Insert logic to handle update of person's name
    ....
  }
  // Invoke the superclass's implementation if it implements this!
  [super observeValueForKeyPath:keyPath
                       ofObject:object
                         change:change
                        context:context];
}
...

@end

The NSKeyValueObserving informal protocol defines the API for key-value observing. Its methods enable change notification, registering observers, notifying observers of changes, and customizing observing. NSObject provides the default implementation of the NSKeyValueObserving protocol, and thus any class that descends from NSObject has built-in support for key-value observing. In addition, you can further refine notifications by disabling automatic observer notifications and implementing manual notifications using the methods in this protocol.

Key-Value Observing or Notifications

In Chapter 12, you learned about the Foundation Framework notification classes (NSNotification, NSNotificationCenter, NSNotificationQueue). These APIs provide a mechanism for passing information between objects, similar to key-value observing. Because this is somewhat similar to the function of key-value observing, you may be wondering, “How do these two mechanisms compare, and which do you use?” Well, there are a variety of differences between key-value observing and the notification APIs that will influence which you choose for the scenario in question, so I’ll compare the two here.

An NSNotification instance encapsulates generic information, so it supports a wide class of system events (including property changes), whereas key-value observing is strictly for notification of changes to properties of objects. As such, the KVO API is potentially simpler to use (vs. the notifications API) for property changes alone.

The notification classes employ a broadcast model of interaction, whereby information (encapsulated in an NSNotification object) is distributed via a centralized notification center (NSNotificationCenter instance). It allows a message to be sent to more than one object, and doesn’t even require receivers to be registered to support notifications. The notification classes support both synchronous and asynchronous (using an NSNotificationQueue instance) posting of notifications. Key-value observing, on the other hand, utilizes a point-to-point interaction model, whereby the subject directly sends a notification to registered observers when a property changes, and blocks until the corresponding method finishes execution. Both mechanisms provide loose coupling; however, the notification APIs also provide nonblocking (i.e., asynchronous) interactions, and thus have the potential to provide greater application responsiveness.

Because the notification mechanism completely decouples the sender and receiver of notification events, there is no mechanism for direct, two-way communication between the two; both must register for notifications in order to provide bidirectional communication. Key-value observing, on the other hand, enables the observer to send a message to the subject, because the subject is provided as a parameter to the observer’s observeValueForKeyPath:ofObject:change:context: method.

A notification is distinguished by its name, and hence the name must be unique to ensure that posted notifications are received by the correct observer(s). Apple specifies a set of naming conventions in the Coding Guidelines for Cocoa document to minimize the possibility of notification name collisions. Because property names are specific to a class (i.e., have class namespace) and subjects are directly bound to observers, naming collisions are not a concern.

Key-Value Observing APIs

The NSKeyValueObserving protocol APIs provide methods to both add and remove observers for an input key path. Invoking the addObserver:forKeyPath:options:context: method creates a connection between the subject and the observer object. Because this creates a strong reference to the observer, an object should be removed as an observer prior to the subject being deallocated. The removeObserver:forKeyPath: method performs this function. It stops an object from receiving change notifications for a property with the specified key path. The addObserver:forKeyPath:context: performs the same function relative to a receiver and a context. When the same observer is registered for the same key-path multiple times, but with different contexts, the context pointer can be used to determine which observer to remove. Listing 18-14 demonstrates the use of these APIs to add and remove an Administrator object as an observer of the name property of a Person instance named person.

Listing 18-14.  KVO Adding and Removing an Observer

Administrator *admin = [Administrator new];
[person addObserver:admin
         forKeyPath:@"name"
            options:NSKeyValueObservingOptionNew
            context:NULL];
[person removeObsserver:admin forKeyPath:@"name"];

Key-value observing provides APIs (declared in the NSKeyValueObserving protocol) to support both automatic and manual key-value change notification. They enable an observer to perform logic both before and after a property value is changed. The APIs support attributes, to-one, and to-many relationship properties.

  • APIs for attributes and to-one relationship properties:
    • willChangeValueForKey:
    • didChangeValueForKey:
  • APIs for to-many, ordered relationship properties:
    • willChange:valuesAtIndexes:forKey:
    • didChange:valuesAtIndexes:forKey:
  • APIs for to-many, unordered relationship properties:
    • willChangeValueForKey:withSetMutation:usingObjects:
    • didChangeValueForKey:withSetMutation:usingObjects:

The default NSObject implementation automatically invokes the correct change notification methods on an observer when a corresponding property value is changed. Automatic/manual change notification is configured with the NSKeyValueObserving automaticallyNotifiesObserversForKey: method. The default NSObject implementation returns YES for this method, hence it performs automatic change notification. To perform manual change notification, your class must override the NSKeyValueObserving automaticallyNotifiesObserversForKey: method. For properties, you want to perform manual change notification; the method should return NO. In this case, your code must invoke the NSKeyValueObserving protocol method willChange: before changing the property value, and the didChange: method after changing the value. Listing 18-15 provides an example implementation of manual change notification for the name property of the Person class, as shown in Figure 18-1.

Listing 18-15.  Example Implementation of KVO Manual Change Notification

- (void)changeName:(Name *)newName
{
  [self willChangeValueForKey:@"name"];
  self.name = newName;
  [self didChangeValueForKey:@"name"];
}

So why would you want to perform manual change notification? Reasons include more fine-grained control over the sending of change notifications, eliminating unnecessary notifications (e.g., cases when the new property value is the same as its old value), and grouping notifications.

The NSKeyValueObserving protocol also provides several methods for customizing observing. The keyPathsForValuesAffectingValueForKey: method retrieves the key paths for all properties whose values affect the value of the input key. This method is used to register dependent keys, when the value of one property depends upon that of one or more properties in another object. For to-one relationship properties, this method should be overridden to enable your code to return the appropriate set of dependent keys for the property. As an example, the Address class, as shown in Figure 18-1, declares street , city , state, and zip properties. A zip property depends on the other three properties; thus, an application observing the zip property should be notified if any of the other address properties changes. This can be accomplished by having the Address class override the method to return a collection of key paths consisting of the keys street , city and state, as shown in Listing 18-16.

Listing 18-16.  Registering Dependent Keys for To-One Relationship Properties

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
  NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
  if ([key isEqualToString:@"zip"])
  {
    NSArray *affectingKeys = @[@"street", @"city", @"state"];
    keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
  }
  
  return keyPaths;
}

It is also possible to register dependent keys for to-one relationship properties by implementing a class method that follows the naming convention keyPathsForValuesAffecting<Key>, where <Key> is the name of the attribute that is dependent on the values. Comparable methods and techniques are provided to register dependent keys for to-many relationship properties.

The observationInfo and setObservationInfo: methods enable you to get/set observation information about all the observers that are registered with the subject.

KVO Design and Implementation

Key-value observing is implemented through the power of the Objective-C runtime. When you register an observer on an object, the KVO infrastructure dynamically creates a new subclass of the object’s original class. It then adjusts the isa pointer of the object to point to the subclass; thus messages to the original class will actually be sent to the subclass. You may recall from Chapter 8 that the isa pointer points to the object’s class and this class maintains a dispatch table with pointers to the methods the class implements. The new subclass implements the KVO infrastructure. It intercepts messages to the object and inspects them for those matching certain patterns (such as the getters, setters, and collection access), posting notifications to observers, and so forth. This technique of dynamically swapping out the class that the isa pointer points to is called isa-swizzling.

Using Key-Value Programming

Now you will implement an example program that uses key-value programming to perform a variety of tasks. The program demonstrates the use of key-value coding and key-value observing in a system whose class diagram is depicted in Figure 18-2.

9781430250500_Fig18-02.jpg

Figure 18-2. Coders class diagram

In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify Coders for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

You’re going to create several classes here that declare various properties. Select New image File . . . from the Xcode File menu, select the Objective-C class template, and name the class Person. Select the Coders folder for the files location and the Coders project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the Person.h file and update the class interface, as shown in Listing 18-17.

Listing 18-17.  Person Class Interface

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (readonly) NSString *fullName;
@property NSString *firstName;
@property NSString *lastName;

- (id)initWithFirstName:(NSString *)fname lastName:(NSString *)lname;

@end

The interface declares three properties and a single initialization method. A Person has a first name, last name, and full name.  The fullName property is read-only, and is derived from the firstName and lastName properties. Next, use the Xcode project navigator to select the Person.m file and code the implementation, as shown in Listing 18-18.

Listing 18-18.  Person Class Implementation

#import "Person.h"
#define CodersErrorDomain   @"CodersErrorDomain"
#define kInvalidValueError  1

@implementation Person

- (id)initWithFirstName:(NSString *)fname lastName:(NSString *)lname
{
  if ((self = [super init]))
  {
    _firstName = fname;
    _lastName = lname;
  }
  
  return self;
}

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

- (BOOL)validateLastName:(id *)value error:(NSError * __autoreleasing *)error
{
  // Check for nil value
  if (*value == nil)
  {
    if (error != NULL)
    {
      NSDictionary *reason = @{NSLocalizedDescriptionKey:
                               @"Last name cannot be nil"};
      *error = [NSError errorWithDomain:CodersErrorDomain
                                   code:kInvalidValueError
                               userInfo:reason];
    }
    return NO;
  }
  // Check for empty value
  NSUInteger length = [[(NSString *)*value stringByTrimmingCharactersInSet:
                       [NSCharacterSet whitespaceAndNewlineCharacterSet]] length];
  if (length == 0)
  {
    if (error != NULL)
    {
      NSDictionary *reason = @{NSLocalizedDescriptionKey:
                               @"Last name cannot be empty"};
      *error = [NSError errorWithDomain:CodersErrorDomain
                                   code:kInvalidValueError
                               userInfo:reason];
    }
    return NO;
  }
  return YES;
}

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

@end

The initWithFirstName:lastName: method is used to initialize a Person instance with the input first and last names. The accessor method fullName returns the derived value of a Person object’s full name as the concatenation of the firstName and lastName property values. The validateLastName:error: method is used to perform KVC property validation on the lastName property. It returns NO and an NSError object if the input last name is nil or an empty string; otherwise, it returns YES. The keyPathsForValuesAffectingValueForKey: method registers dependent keys. The method utilizes the fullName property, which depends on the firstName and lastName properties, to obtain the keys that are added to the returned key path.

Next, you will implement the Coder class. Select New image File . . . from the Xcode File menu, select the Objective-C class template, and name the class Coder. Select the Coders folder for the files location and the Coders project as the target, and then click the Create button. In the Xcode project navigator pane, select the Coder.h file and update the class interface, as shown in Listing 18-19.

Listing 18-19.  Coder Class Interface

#import <Foundation/Foundation.h>

@class Person;
@interface Coder : NSObject

@property Person *person;
@property NSMutableArray *languages;

@end

The class declares two properties: a Person property named person, and an NSMutableArray property named languages. The languages property is an array that contains the names of programming languages that a Coder uses. Notice that a @class directive is used to forward declare the Person class, thereby removing the need to import its header file in this interface. Now use the Xcode project navigator to select the Coder.m file and code the implementation, as shown in Listing 18-20.

Listing 18-20.  Coder Class Implementation

#import "Coder.h"

@implementation Coder

- (NSUInteger)countOfLanguages
{
  return [self.languages count];
}

- (NSString *)objectInLanguagesAtIndex:(NSUInteger)index
{
  return [self.languages objectAtIndex:index];
}

- (void)insertObject:(NSString *)object inLanguagesAtIndex:(NSUInteger)index
{
  [self.languages insertObject:object atIndex:index];
}

- (void)removeObjectFromLanguagesAtIndex:(NSUInteger)index
{
  [self.languages removeObjectAtIndex:index];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  NSString *newValue = change[NSKeyValueChangeNewKey];
  NSLog(@"Value changed for %@ object, key path: %@, new value: %@",
        [object className], keyPath, newValue);
}

@end

The Coder implementation includes the KVO-recommended methods for the ordered, to-many relationship of the languages property. The observeValueForKeyPath:ofObject:change:context: method is executed when a Coder property with the input key path is changed. In this case, the method just prints out the new value for the object and key path in question.

Finally, you will implement the Coders class. As depicted in Figure 18-2, the Coders class holds a collection (an NSSet) of Coder instances. Select New image File . . . from the Xcode File menu, select the Objective-C class template, and name the class Coders. Select the Coders folder for the files location and the Coders project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the Coders.h file and update the class interface, as shown in Listing 18-21.

Listing 18-21.  Coders Class Interface

#import <Foundation/Foundation.h>

@class Coder;
@interface Coders : NSObject

@property NSSet *developers;

@end

The Coders interface declares a single property, an NSSet named developers that contains the collection of Coder objects. Now use the Xcode project navigator to select the Coders.m file and code the implementation, as shown in Listing 18-22.

Listing 18-22.  Coders Class Implementation

#import "Coders.h"

@implementation Coders

- (NSUInteger)countOfDevelopers
{
  return [self.developers count];
}

- (NSEnumerator *)enumeratorOfDevelopers
{
  return [self.developers objectEnumerator];
}

- (Coder *)memberOfDevelopers:(Coder *)member object:(Coder *)anObject
{
  return [self.developers member:anObject];
}

@end

The Coders implementation includes the KVO-recommended methods for the unordered, to-many relationship of the developers property.

Now that you have finished implementing the classes, let’s move on to the main() function. In the Xcode project navigator, select the main.m file and update the main() function, as shown in Listing 18-23.

Listing 18-23.  Coders main( ) function

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Coder.h"
#import "Coders.h"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    Person *curly = [[Person alloc] initWithFirstName:@"Curly" lastName:@"Howard"];
    NSLog(@"Person first name: %@", [curly valueForKey:@"firstName"]);
    NSLog(@"Person full name: %@", [curly valueForKey:@"fullName"]);
    NSArray *langs1 = @[@"Objective-C", @"C"];
    Coder *coder1 = [Coder new];
    coder1.person = curly;
    coder1.languages = [langs1 mutableCopy];
    NSLog(@" Coder name: %@   languages: %@",
          [coder1 valueForKeyPath:@"person.fullName"],
          [coder1 valueForKey:@"languages"]);
    
    Coder *coder2 = [Coder new];
    coder2.person = [[Person alloc] initWithFirstName:@"Larry" lastName:@"Fine"];
    coder2.languages = [@[@"Objective-C", @"C++"] mutableCopy];
    NSLog(@" Coder name: %@   languages: %@",
          [coder2 valueForKeyPath:@"person.fullName"],
          [coder2 valueForKey:@"languages"]);
    
    [curly addObserver:coder1
            forKeyPath:@"fullName"
               options:NSKeyValueObservingOptionNew
               context:NULL];
    curly.lastName = @"Fine";
    [curly removeObserver:coder1 forKeyPath:@"fullName"];
    
    Coders *bestCoders = [Coders new];
    bestCoders.developers = [[NSSet alloc] initWithArray:@[coder1, coder2]];
    [bestCoders valueForKey:@"developers"];
    NSLog(@"Number of coders = %@", [bestCoders.developers
                                     valueForKeyPath:@"@count"]);
    NSError *error;
    NSString *emptyName = @"";
    BOOL valid = [curly validateValue:&emptyName forKey:@"lastName" error:&error];
    if (!valid)
    {
      NSLog(@"Error: %@", ([error userInfo])[NSLocalizedDescriptionKey]);
    }
  }
  return 0;
}

The function first creates a Person instance and then uses key-value coding to retrieve values for its properties. Next, a Coder instance is created, and its person and languages property are set. The key-value coding APIs are then used to retrieve the corresponding property values. Another Coder instance is then created and its property values are set accordingly. Key-value observing is then demonstrated, with a Coder instance registered to receive notifications when the fullName property of a Person instance is changed. A Coders instance (remember, the Coders class holds a collection of Coder instances) is then created and its developers property is assigned to the collection of Coder instances created previously. A collection operator is then used to return the total number of coders in the collection. Finally, key-value coding validation is demonstrated on the lastName property of a Person instance.

When you compile and run the program, you should observe the messages in the output pane shown in Figure 18-3.

9781430250500_Fig18-03.jpg

Figure 18-3. Coders program output

Excellent! This completes your in-depth examination of the key-value programming APIs and infrastructure. Knowledge of key-value coding and key-value observing will be extremely useful to you when programming with Objective-C and using Apple’s various software frameworks and services.

Roundup

In this chapter, you learned about key-value programming, a powerful set of mechanisms and APIs in the Objective-C language. Key-value programming is an important component of general Objective-C programming, along with the Cocoa and Cocoa Touch frameworks, hence it is important to understand its details and how it can be effectively used in your programs. This chapter provided an in-depth examination of key-value programming infrastructure, specifically key-value coding and key-value observing. Its key takeaways include the following:

  • Key-value coding enables your code to access and manipulate an object’s properties indirectly, by name (i.e., key), rather than through accessor methods or backing instance variables.
  • Key-value coding allows you to access a property using a string that can be varied at runtime, and thus provides a more dynamic, flexible approach for accessing and manipulating an object’s state. Its key benefits include loose coupling, configuration-based property access, and code simplification.
  • Key-value coding uses keys and key paths for property access. A key is a string that identifies a specific property. A key path specifies a sequence of object properties (within an object graph) that are traversed to identify a specific property. The KVC APIs support accessing both individual and multiple properties of an object. The properties can be primitives, C structs, object types (including collections). Primitives and structs are automatically wrapped to/from corresponding object types.
  • The NSKeyValueCoding informal protocol defines the mechanism for accessing objects indirectly by name/key. This protocol declares the key-value coding API, consisting of class and instance methods along with constant values. The methods enable you to get and set property values, perform property validation, and change the default behavior of the key-value coding methods for getting property values.
  • Key-value coding utilizes a mechanism for mapping keys to the appropriate property accessor methods and/or property backing variables. This mechanism utilizes a set of standard search patterns that rely on property accessor method naming conventions.
  • Key-value coding also includes infrastructure for validating property values. The KVC validation mechanism comprises a set of APIs and a standard convention for custom property validation methods. The KVC validation methods can be called directly or indirectly.
  • The key-value coding collection operators allow actions to be performed on the items of a collection using key path dot notation.
  • Key-value observing enables other objects to be notified of changes to the properties of other objects. It is essentially an implementation of the observer software design pattern, and is implemented in numerous software libraries and frameworks. In fact, it is a key component of the model-view-controller (MVC) design pattern, a central component of the Cocoa and Cocoa Touch frameworks. Key-value observing is built on key-value coding.
  • Key-value observing provides numerous benefits, including decoupling of observer objects from the object being observed (i.e., the subject), framework-level support, and a full-featured set of APIs. It provides event notification, similar to the Foundation Framework notification APIs, but a different set of features. Hence, you should compare the two before deciding which to use in your programs.
  • Key-value observing provides APIs that support both automatic and manual key-value property change notification. They enable an observer to perform logic both before and after a property value is changed. The APIs support attributes, to-one, and to-many relationship properties. Automatic property change notification is implemented by the default NSObject KVO implementation. Manual change notification provides more fine-grained control over the sending of change notifications, eliminates unnecessary notifications (e.g., for cases when the new property value is the same as its old value), and enables you to group notifications.

Congratulations, you should now be well on your way to mastering the Objective-C language! Perhaps there’s some material that you want to review again, or maybe you would just like to give your brain a well-deserved rest. Go right ahead, but please take advantage of the numerous code listings and programs to thoroughly explore the topics covered. The more you do this, the more skilled you will become at Objective-C programming.

This is the final chapter of this book—but guess what, you’re not done yet. The next stop is the appendices, where you’ll explore a variety of additional topics that will further enhance your Objective-C skills.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.183.10