键值编码(KVC)是Cocoa一个通用的用于获取和设定值的一个协议。在编程中,“通用”这词是用于描述一种可以适用于不同情境的实现方式。通用代码可以减少项目中代码总量并使得软件可以处理程序员无法预知的情景。Cocoa至始至终都很重视通用、可复用代码。
比如,设定一个对象first name和lastname的一种不通用的方式如下:
[person setFirstName: @"Scott"];
[person setLastName: @"Stevenson"];
这挺好的,不过通过使用KVC消息可以写更通用的代码:
[person setValue:@"Scott" forKey:@"firstName"];
[person setValue:@"Stevenson" forKey:@"lastName"];
新手们可能会质疑这个的意义。实际上,KVC版看起来需要更多代码输入。让就让我选择一个更能体现KVC价值的场景。
首先,我们定义一个类:
@interface CDCPerson : NSObject
{
NSString * firstName;
NSString * lastName;
NSNumber * phoneNumber;
NSString * emailAddress;
}
- (void) setFirstName: (NSString *)value;
- (void) setLastName: (NSString *)value;
- (void) setPhoneNumber: (NSNumber *)value;
- (void) setEmailAddress: (NSString *)value;
@end
一些实际使用代码如下:
// 假设inputValues包含我们想设定到person上的键值
NSDictionary *inputValues;
CDCPerson *person = [[CDCPerson alloc] init];
NSEnumerator *e = [inputValues keyEnumerator];
id dictKey, dictValue;
while ( dictKey = [e nextObject] )
{
dictValue = [inputValues valueForKey: dictKey];
[person setValue: dictValue forKey: dictKey];
}
该代码段是通用的,意味着我们不需要在每次Person类添加新的实例变量的时候改变代码。
但是还可以更好!如下就是一个上述代码的简化版本:
// 假设inputValues包含我们想设定到person上的键值
NSDictionary *inputValues;
CDCPerson *person = [[CDCPerson alloc] init];
[person setValuesForKeysWithDictionary: inputValues];
很隐晦吧?如下是Apple关于-setValuesForKeysWithDictionary:实现机制的解释:
通过键值对设定消息接收方的属性值,键用于识别各个属性。默认实现是对每个键值对调用setValue:forKey:,在键值对有NSNull值的情况下替换成nil。
换句话说,这和第一个例子没有什么区别。但是-setValue:forKey:到底做了些什么呢?这就是KVC的魔力所在。它实际上会找到-setFirstName:、-setLastName:、setPhoneNumber: 以及-setEmailAddress: 的实现并调用它们。如果无法找到这些,KVC会尝试几种可能直至最终将值设定到实例变量上。
KVC也可以用来从一个对象中获取值:
// 假设person已经存在并被赋值
CDCPerson *person;
NSMutableDictionary *outputValues;
outputValues = [NSMutableDictionary dictionary];
NSArray *keys;
keys = [NSArray arrayWithObjects: @"firstName",
@"lastName",
@"phoneNumber",
@"emailAddress",
nil];
NSEnumerator *e = [keys objectEnumator];
id key, value;
while ( key = [e nextObject] )
{
value = [person valueForKey: key];
[outputValues setValue: value forKey: key];
}
或者更简单的版本:
// 假设person已经存在并被赋值
CDCPerson *person;
NSArray *keys;
keys = [NSArray arrayWithObjects: @"firstName",
@"lastName",
@"phoneNumber",
@"emailAddress",
nil];
NSDictionary *outputValues;
outputValues = [person dictionaryWithValuesForKeys: keys];
和设定值一样,通过-valueForKey:获取值让KVC去查找和键值有相同名字的方法:
// 这回让KVC查找一个名为-firstName的方法
NSString *name = [person valueForKey:@"firstName"];
键值编码是Cocoa Bindings和Core Data的关键,所以了解一些基础知识是很值得的。KVC可以处理如下一些键值路径:
// 获取
[obj valueForKeyPath: @"storage.firstName"];
// 设定
[obj setValue: @"Scott" forKeyPath: @"storage.firstName"];
作用同如下代码:
// getting
[[obj storage] firstName];
// setting
[[obj storage] setFirstName:@"Scott"];