Objective_C_08_1_KVC
15/12/15
// Objective_C_08_1_KVC
键/值编码
-valueForKey:
-setValue:forKey:
1.KVC不属于Obj-C语言的特性,而是Cocoa提供的一种特性。
2.键/值编码中的基本调用包括-valueForKey:和-setValue:forKey:。
以字符串的形式向对象发送消息,这个字符串使我们关注的属性的关键。
valueForKey:首先查找以键-key或-isKey命名的getter方法。
valueForKey:查找-name和-make。如果不存在getter方法,他将在对象内部查找名为_key或key的实例变量。如果我们没有通过@synthesize提供存取方法,valueForKey将会查找实例变量_name和name,或_make和make。注意,-valueForKey是在Obj-C运行时中使用元数据打开对象并进入其中查找需要的信息。在C或C++语言中不能执行这种操作。通过使用KVC,可以获取不存在getter方法的对象值,无需通过对象指针直接访问实例变量。即无视访问权限。
可以对年份对象使用相同的技术:NSLog(@"model year is %@",[car valueForKey:@"modelYear"]);等 等!NSLog中的%@输出一个对象,但modelYear是一个int值,而不是对象。该如何处理呢?对于KVC,Cocoa自动放入和取出标量值。也就是说,当使用setValue:ForKey:时,他自动将标量值(int、float、struct)放入NSNumber或NSValue中;当使用-valueForKey时,他自动将标量值从这些对象中取出。仅KVC具有这种自动包装功能。常规方法调用和属性语法不具备该功能。
除用于检索值外,还可以使用-setValue:forKey:按名称设置值:
[car setValue:@"Harold" forKey:@"name"];
这个方法的工作方式和-valueForKey:相同。他首先查找名称setter方法,例如-setName,并使用参数@"Harold"调用他。如果不存在setter方法,他将在类中查找名为name或_name的实例变量,然后为他赋值。
谨记这条规则:编译器和苹果公司都以下划线开头的形式保存实例变量名称,如果你尝试在其他地方使用下划线,可能会出现严重的错误。这条规则实际上不是强制的,但如果不遵循他,你可能会遇到某种风险。
3.除了通过键设置值外,键/值还支持指定键路径,像文件系统路径一样,你可以遵循一系列关系来指定该路径。
[car setValue:[Number numberWithInt:155] forKeyPath:@"engine.horsepower"];
NSLog(@"horsepower is %@",[car valueForKeyPath:@"engine.horsepower"]);
这 些路径的深度是任意的,具体取决于对象图(对象图是一种表示相关对象集合的有趣方式)的复杂度,可以使用诸如 “car.interior.airconditioner.fan.velocity”这样的键路径。在某种程度上,使用键路径比使用一系列嵌套方法调 用更容易访问对象。
关于KVC非常棒的一点是,如果向NSArray请求一个键值,他实际上会查询数组中的每个对象来查找这个键值,然后将查询结果打包到另一个数组中并返回给你。这种方法也适用于通过键路径访问的对象内部的数组。
在KVC中,通常认为嵌入到其他对象中的NSArray具有一对多的关系。例如,汽车能与多个轮胎存在联系。因此,我们可以说Car与Tire之间存在1对多的关系。如果键路径中含有一个数组属性,则该键路径的其余部分将被发送给数组的每个对象。
NSArray *pressures=[car valueForKeyPath:@"tires.pressure"];
NSLog(@"pressures %@",pressures);
然而,不能在键路径中为这些数组添加索引,例如,通过使用"tires[0].pressure"获取第一个轮胎的压力值
4.键路径不仅能引用对象值,还可以引用一些运算符来进行一些运算,例如获取一组值的平均值或返回这组值的最小值和最大值。
例如:
NSNumber *count;
count=[garage valueForKeyPath:@"cars.@count"];
我们将键路径@"cars.@count"拆开,cars用于获取cars属性,@count的@符号意味着后面将进行一些运算,这里是计算键路径左侧结果。
NSNumber *sum;
sum=[garage valueForKeyPath:@"cars.@sum.mileage"];
这项功能是如何实现的?@sum运算符将键路径分为两部分。第一部分可以看成是多对多关系的键路径,另一部分可以看成包含一对多关系的任何键路径。他被当作用于关系中每个对象的键路径。
NSNumber *agvMileage;
agvMileage=[garage valueForKeyPath:@"cars.@avg.mileage"];
min=[garage valueForKeyPath:@"cars.@min.mileage"];
max=[garage valueForKeyPath:@"cars.@max.mileage"];
5.KVC 能轻松地处理集合。那么,为什么不使用KVC来处理所有对象,并且抛弃存取方法和代码编写?KVC需要解析字符串来计算你需要的答案。因而速度较慢。另 外,编译器还无法对她进行错误检查。你可能想要处理karz.@avg.mileage:,但编译器不能判断他是否是错误的键路径。因此,当你尝试使用它 时,就会出现运行时错误。
有时,使用的属性仅含有一小组值,例如上面构造的所有汽车。即使我们有100万量汽车,品牌的种类也会很少。通过使用键路径“cars.@distinctUnionOfObjects.make”,就可以从集合中得到需要的品牌。
NSArray *manufacturers;
manufacturers=[garage valueForKeyPath:@"cars.@disctinctUnionOfObjects.make"];
NSLog(@"makers:%@",manufacturers);
他 还支持在用户界面代码中实现一些不错的功能,例如:通过苹果公司的Aperture Lift and Stamp工具,可以在其他图片上更改某个图片的部分内容。可以使用dictionaryWithValuesForKeys方法调整所有属性,并将字典 的所有内容显示在用户界面中。用户可以从字典中读取这些属性,然后调用setValuesForKeysWithDictionary方法,使用修改过的 字典更改其他图片。设计好用户界面类似后,可以将相同的Lift and Stamp面板应用于不同的对象,例如照片、汽车和事物。
由于字典不能含有nil值,如果出现nil值将会出现什么情况(例如汽车没有名称)?将返回[NSNull null]。也可以为
setValuesForKeysWithDictionary提供[NSNull null]。
6.nil仍然可用
对nil值的讨论引出了一个有趣的问题。标量值中的“nil”表示什么?0?-1?表示pi?Cocoa无法知道nil表示什么,你可以尝试以下代码:
[car setValue:nil forKey:@"mileage"];
但Cocoa给出以下警告信息:
[<Car 0x105740> setNilValueForKey]: could not set nil as the value for the key mileage.
为了解决这个问题,可以重写-setNilValueForKey,提供逻辑上有意义的任何值。在此,我们约定,nil mileage表示清除汽车行驶的距离,而不是使用其他值(例如-1)来实现此功能:
- (void) setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"mileage"]) {
mileage=0;
} else {
[super setNilValueForKey:key];
}
}
7.处理未定义的键:
如果你使用过KVC,并且输入了错误的键,你可能会看到以下消息:
[<Car 0x105740> valueForUndefinedKey:]: this class is not key value coding-compliant for the key garbanzo.
以上消息的主要含义是,Cocoa无法确定使用这个键的意图,因此放弃了操作。
如果仔细分析错误消息,你会注意到,他提到了valueForUndefinedKey:方法。也许你能够猜到,我们可以通过重写该方法来处理未定义的键。也许你还能猜到,如果要更改未知键的值,还可以使用setValue:forUndefinedKey:方法。
如果KVC机制无法找到处理方式,他会返回询问类如何处理。默认实现会取消操作,如前文所示。但我们可以更改默认行为。我们将Garage转换为一个非常灵活的对象,通过这个对象可以设置或获取任何键。我们首先添加一个可变字典。
@interface Garage:NSObject {
NSString *name;
NSMutableArray *cars;
NSMutableDictionary *stuff;
}
@end
接下来添加valueForUndefinedKey方法:
- (void) setValue:(id)value forUndefinedKey:(NSString *)key {
if (stuff==nil) {
stuff=[[NSMutableDictionary alloc] init];
}
[stuff setValue:value forKey:key];
}
- (id) valueForUndefinedKey:(NSString *)key {
id value=[stuff valueForKey:key];
return (value);
}
别忘了使用-dealloc释放字典
现在可以设置garage上的任何值:
[garage setValue:@"bunny" forKey:@"fluffy"];
[garage setValue:@"bunny" forKey:@"bork"];
[garage setValue:[NSNull null] forKey:@"snorgle"];
[garage setValue:nil forKey:@"gronk"];
返回他们:
NSLog(@"value are %@ %@ %@ and %@",
[garage valueForKey:@"fluffy"],
[garage valueForKey:@"bork"],
[garage valueForKey:@"snorgle"],
[garage valueForKey:@"gronk"]);
输出:
values are bunny greeble <null> and (null)
注意<null>和(null)之间的区别。<null>是[NSNull null]对象,而(null)是一个真实存在的nil值。由于字典中没有“gronk”,所以此处我们得到了nil值。还要注意,在使用stuff字典时,我们使用KVC的setValue:forKey:方法,通过这种方法,调用者可以直接传入nil值,我们不必在代码中检查他。如果为NSDictionary setObject:forKey:提供nil值,他将会给出警告信息。可是,如果在字典中将setValue:forKey:设置成nil值,将会把对应的键从字典中删除。
8. 通过字典实现批量的数据存储
NSDictionary *dic = @{@"name”:@“lusy”,@“sex”:@“女”,@“age":@(12)};
Person *p = [[Person alloc] init];
[p setValuesForKeysWithDictionary:dic];
NSLog(@"%@--%@--%ld",p.name,p.sex,p.age);
9.小结
KVC用法小结:
valueForKey
setValue:forKey:
valueForKeyPath
setValue:forKeyPath:
setValue: forUndefinedKey:
setValuesForKeysWithDictionary
键路径:
@"engine.horsepower"
运算功能:
@"cars.@count"
@"cars.@avg.mileage"
@"cars.@min.mileage"
@"cars.@max.mileage"
@"cars.@disctinctUnionOfObjects.make"
通过查看错误信息中的出错函数信息来重写函数,如下:
[<Car 0x105740> setNilValueForKey]: could not set nil as the value for the key mileage.
可以看出是setNilValueForKey出错了,可以重写该函数,即自定义该函数。一般函数中除了自定义的部分外要调用[super setNilValueForKey]
[<Car 0x105740> valueForUndefinedKey:]: this class is not key value coding-compliant for the key garbanzo.
可以看出是valueForUndefinedKey:函数出错了
NSDictionary setObject:forKey: 不能为对象赋nil值,因为NSDictionary不能存储nil值
setValue:forKey: 则可以使用nil值,其含义是从NSDictionary中删除相应的键。

浙公网安备 33010602011771号