!!!Obj-c on Mac --- Chapter 16 Key-Value Coding

Keyvalue coding, affectionately known as KVC to its friends, is a way of changing an object’s state indirectly, by using strings to describe what piece of object state to change.

NSKeyValueCoding protocol

KVC extensions

Introducing KVC

The fundamental calls in key-value coding are -valueForKey: and -setValue:forKey:.

// looks for -name
NSString *name = [car valueForKey:@"name"];
NSLog (@"%@", name);

valueForKey: works by first looking for a getter named after the key: -key or -isKey. If there is no getter method, it looks inside the object for an instance variable named _key or key. If we had not supplied accessor methods via @synthesize, valueForKey would look for the instance variables _name and name.

-valueForKey uses the metadata in the Objective-C runtime to crack open objects and poke inside them looking for interesting information. You can’t really do this kind of stuff in C or C++. By using KVC, you can get values where there are no getter methods and without having to access an instance variable directly via an object pointer.

For KVC, Cocoa automatically boxes and unboxes scalar values. That is, it automatically puts scalar values (ints, floats, and some structs) into NSNumbers or NSValues when you use valueForKey, and it automatically takes scalar values out of these objects when you use -setValueForKey. Only KVC does this autoboxing. Regular method calls and property syntax don’t do this.

NSLog (@"model year is %@", [car valueForKey: @"modelYear"]);
In addition to retrieving values, you can set values by name by using -setValue:forKey:

[car setValue: @"Harold" forKey: @"name"];
If you’re setting a scalar value, before calling -setValue:forKey:, you need to wrap it up:

[car setValue: [NSNumber numberWithFloat: 25062.4]
forKey: @"mileage"];
-setValue:forKey: will unbox the value before it calls -setMileage: or changes the mileage instance variable.

Key Path

In some ways, digging into your objects can be easier with a key path than doing a series of nested method calls.

// here you ask engine for its component
[engine setValue: [NSNumber numberWithInt: 150]            
forKey: @"horsepower"];
// you can also ask a car for its component's component ...
[car setValue: [NSNumber numberWithInt: 155]
forKeyPath: @"engine.horsepower"];
NSLog (@"horsepower is %@", [car valueForKeyPath: @"engine.horsepower"]);

KVC and Array

One cool thing about KVC is that if you ask an NSArray for a value for a key, it will actually ask every object in the array for the value for that key and then pack things up in another array, which it gives back to you. The same works for arrays that are inside of an object (recall composition?) that you access by key path.

NSArrays that are embedded in other objects are known in the KVC vernacular as having to-many relationship. So we can say that Car has a to-many relationship with Tire.
If a key path includes an array attribute, the remaining part of the key path is sent to every object in the array.

// You will get an array with 4 tire pressure values
NSArray *pressures = [car valueForKeyPath: @"tires.pressure"];
NSArray implements valueForKeyPath: by looping over its contents and sending each object the message.
Unfortunately, you can’t index these arrays in the key path, such as by using "tires[0].pressure" to get to the first tire.

Smooth Operator

Key paths can refer to more than object values. A handful of operators can be stuck into the key paths to do things like getting the average of an array of values or returning the minimum and maximum of those values.

NSNumber *count;
count = [garage valueForKeyPath: @"cars.@count"];
NSLog (@"We have %@ cars", count);
Let’s pull apart this key path, "cars.@count". cars says to get the cars property, which we know is an NSArray, from garage. Well, OK, we know it’s an NSMutableArray, but we can consider it just to be an NSArray if we’re not planning on changing anything. The next thing is @count. For the compiler, @"blah" is a string, and @interface is the introduction for a class. Here, @count tells the KVC machinery to take the count of the result of the left-hand part of the key path.
We can also get the sum of a particular value, like the total number of miles our fleet has covered.
NSNumber *sum;
sum = [garage valueForKeyPath: @"cars.@sum.mileage"];
NSLog (@"We have a grand total of %@ miles", sum);
NSNumber *avgMileage;
avgMileage = [garage valueForKeyPath: @"cars.@avg.mileage"];
NSLog (@"average is %.2f", [avgMileage floatValue]);

Sometimes, you have an attribute that can take on only a small set of values, like the make of all of the cars. Even if we had a million cars, we would have a small number of unique makes. You can get just the makes from your collection with the key path "cars.@distinctUnionOfObjects.make":

NSArray *manufacturers;
manufacturers =
[garage valueForKeyPath: @"cars.@distinctUnionOfObjects.make"];
NSLog (@"makers: %@", manufacturers);

KVC IS NOT FOR FREE

KVC makes digging around in collections pretty easy. So why not use KVC for everything, then, and forget about accessor methods and writing code?

KVC is necessarily slower, because it needs to parse strings to figure out what it is you want. There is also no error checking by the compiler. You might ask for karz.@avg.millage: the compiler has no idea that’s a bad key path, and you’ll get a runtime error when you try to use it.

Life’s a Batch

KVC has a pair of calls that let you make batch changes to objects. The first is dictionaryWithValuesForKeys:. You give it an array of strings. The call takes the keys, uses valueForKey: with each of the keys, and builds a dictionary with the key strings and the values it just got.

car = [[garage valueForKeyPath: @"cars"] lastObject];
NSArray *keys = [NSArray arrayWithObjects: @"make", @"model",
@"modelYear", nil];
NSDictionary *carValues = [car dictionaryWithValuesForKeys: keys];
NSLog (@"Car values : %@", carValues);
setValuesForKeysWithDictionary can be used to modify attribute values:

NSDictionary *newValues =
[NSDictionary dictionaryWithObjectsAndKeys:
@"Chevy", @"make",
@"Nova", @"model",
[NSNumber numberWithInt:1964], @"modelYear",
nil];
[car setValuesForKeysWithDictionary: newValues];
NSLog (@"car with new values is %@", car);

The Nils Are Alive

[car setValue: nil forKey: @"mileage"];
//'[<Car 0x105740> setNilValueForKey]: 
// could not set nil as the value for the key mileage.'

- (void) setNilValueForKey: (NSString *) key {
    if ([key isEqualToString: @"mileage"]) {
    mileage = 0;
    } else {
    [super setNilValueForKey: key];
    }
} // setNilValueForKey

Handling the Unhandled

If you’ve tried your hand at any KVC and mistyped a key, you’ve probably seen this:

'[<Car 0x105740> valueForUndefinedKey:]: this class is not key value
coding-compliant for the key garbanzo.'
we can handle undefined keys by overriding valueForUndefinedKey: and setValue:forUndefinedKey:

@interface Garage : NSObject {
NSString *name;
NSMutableArray *cars;
NSMutableDictionary *stuff;
}
...
@end // Garage

- (void) setValue: (id) value forUndefinedKey: (NSString *) key {
if (stuff == nil) {
stuff = [[NSMutableDictionary alloc] init];
}
[stuff setValue: value forKey: key];
} // setValueForUndefinedKey
- (id) valueForUndefinedKey:(NSString *)key {
id value = [stuff valueForKey: key];
return (value);
} // valueForUndefinedKey

<null> is an [NSNull null] object, and (null) is a real live nil value

accessInstanceVariableDirectly

accessInstanceVariableDirectly can be used to control if class instance variable can be modified by KVC
/* Return YES if -valueForKey:, -setValue:forKey:, -mutableArrayValueForKey:, 
-storedValueForKey:, -takeStoredValue:forKey:, and -takeValue:forKey: 
may directly manipulate instance variables when sent to instances of the receiving class, 
NO otherwise. The default implementation of this method returns YES.
*/
+ (BOOL)accessInstanceVariablesDirectly;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值