Brief Intro to KVC Design and Implementation

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 theNSKeyValueCoding 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 NOindicates 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 NSKeyValueCodingvalueForUndefinedKey: 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-6overrides the valueForUndefinedKey: method of the Hello class implementation shown earlier (see Listing 18-1) to return the value of the property namedgreeting 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 methodaccessInstanceVariablesDirectly 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_isNamename, 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’ssetValue:forUndefinedKey: method is invoked.

The search patterns support various categories of properties (attributes, object types, collections). As mentioned previously, your classes can also override thevalueForUndefinedKey: 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,NSNumberNSDecimalNumberNSValue). A property that conforms to ato-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 (NSArrayNSSet, 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 namedfirstName, 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 beyondNSArray 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-1Methods to Implement for Ordered, To-Many Relationship Properties

Method Ordered, To-Many
Relationship
Mutable Ordered, To-Many
Relationship
countOf<Key>RequiredRequired
objectIn<Key>AtIndex: (or<key>AtIndexes:)RequiredRequired
get<Key>:range:OptionalOptional
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 aPerson class. Imagine a to-many property named people (of type NSArray *).Listing 18-7 provides implementations of the countOf<Key> andobjectIn<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-2Methods to Implement for Unordered, To-many Relationship Properties

Method Ordered, To-Many
Relationship
Mutable Ordered, To-Many
Relationship
countOf<Key>RequiredRequired
enumeratorOf<Key>RequiredRequired
memberOf<Key>:OptionalOptional
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 correspondingvalidate<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-10illustrates an example usage of key-value validation on an Address instance namedaddress 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

collectionKeyPath.@operator.propertyKeyPath

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. Theoperator (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 namedorderItems, 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 @countoperator.

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值