1. 熟练使用KVC 可以在开发过程中可以给我们带来巨大的好处,尤其是在json 转模型的时候,KVC让程序员摆脱了繁琐无营养的代码堆积。减少代码量就是减少出错的概率。KVC 用起来很灵活,这种灵活的基础是严格的命名要求。这种命名要求其实是一种约定。再程序的世界里,约定的作用远远大于开发本身,良好的约定可以使程序员摆脱很多判断,也减少了错误。
1.1.基本概念
1. KVC是KeyValue Coding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。
1.1.基本概念
1. KVC是KeyValue Coding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。
2. 在应用程序中实现键-值编码兼容性是一项重要的设计原则。存取方法可以加强合适的数据封装,而键-值编码方法在多数情况下可简化程序代码。
3. 键-值编码支持带有对象值的属性,同时也支持纯数值类型和结构。非对象参数和返回类型会被识别并自动封装/解封。
使用 KVC 为对象赋值或者取值时,需要知道准确的键值, 相比较点语法,KVC 是一种间接的传递方式,这种方式有利于
对象解耦,让对象彼此之间的耦合度不会太高。
1.2.设置和访问
1.键/值编码中的基本调用包括 -valueForKey: 和 -setValue:forkey: 这两个方法,它们以字符串的形式向对象发送消息,字符串为属性名,
2.是否存在 setter、getter 方法, 若存在优先调用相应方法;若不存在,它将在内部查找名为 _key 或 key 的实例变量。
3.通过 KVC 设置对象,此对象会 retain。
4.通过 setValue:forKey: 设置对象的值,或通过 valueForKey 来获取对象的值时,如若对象的实例变量为基本数据类型时 ( char、int、float、
3. 键-值编码支持带有对象值的属性,同时也支持纯数值类型和结构。非对象参数和返回类型会被识别并自动封装/解封。
使用 KVC 为对象赋值或者取值时,需要知道准确的键值, 相比较点语法,KVC 是一种间接的传递方式,这种方式有利于
对象解耦,让对象彼此之间的耦合度不会太高。
1.2.设置和访问
1.键/值编码中的基本调用包括 -valueForKey: 和 -setValue:forkey: 这两个方法,它们以字符串的形式向对象发送消息,字符串为属性名,
2.是否存在 setter、getter 方法, 若存在优先调用相应方法;若不存在,它将在内部查找名为 _key 或 key 的实例变量。
3.通过 KVC 设置对象,此对象会 retain。
4.通过 setValue:forKey: 设置对象的值,或通过 valueForKey 来获取对象的值时,如若对象的实例变量为基本数据类型时 ( char、int、float、
BOOL ) ,我们需要对数据进行封装。
5.赋值语句 setValue:forKey: 是给对象当前的属性赋值,而 setValue:forKeyPath: 是按照对象的层级关系为其中
的属性赋值。 forKeyPath可以替代forKey,但是forKey不能替代forKeyPath。
KVC中的方法:
2.1 获取值
valueForKey:,传入NSString属性的名字。
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。
2.2 修改值
setValue:forKey:
setValue:forKeyPath: m
setValue:forUndefinedKey:
setNilValueForKey: 当对非类对象属性设置nil时,调用,默认抛出异常。
2.3 一对多关系成员的情况
mutableArrayValueForKey:有序一对多关系成员 NSArray
mutableSetValueForKey:无序一对多关系成员 NSSet
1.3 KVC的实现细节
搜索Setter、Getter方法,这一部分比较重要,能让你了解到KVC调用之后,到底是怎样获取和设置类成员值的。
3.1 搜索简单的成员 如:基本类型成员,单个对象类型成员:NSInteger,NSString*成员。
a. setValue:forKey的搜索方式:
1. 首先搜索set<Key>:方法
如果成员用@property,@synthsize处理,因为@synthsize告诉编译器自动生成set<Key>:格式的setter方法,
所以这种情况下会直接搜索到。注意:这里的<Key>是指成员名,而且首字母大写。下同。
2. 上面的setter方法没有找到,那么类方法accessInstanceVariablesDirectly返回YES(注:这是NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)。
那么按_<key>,_is<Key>,<key>,is<key>的顺序搜索成员名。
3. 如果找到就去设置成员的值,如果没有就去调用setValue:forUndefinedKey:。
b. valueForKey:的搜索方式:
1. 首先按get<Key>、<key>、is<Key>的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。
2. 上面的getter没有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。
如果countOf<Key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。
3. 还没查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。
如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:组合的形式调用。
4. 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,_is<Key>,<key>,is<key>的顺序直接搜索成员名。
5. 再没查到,调用valueForUndefinedKey:。
3.2 查找有序集合成员,比如NSMutableArray
mutableArrayValueForKey:搜索方式如下:
1. 搜索insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexes、remove<Key>AtIndexes:格式的方法。
如果至少一个insert方法和至少一个remove方法找到,那么同样返回一个可以响应NSMutableArray所有方法的代理集合。那么发送给这个代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:atIndexes、remove<Key>AtIndexes:组合的形式调用。还有两个可选实现的接口:replaceObjectIn<Key>AtIndex:withObject:、replace<Key>AtIndexes:with<Key>:。
2. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set<Key>:方法。
也就是说,mutableArrayValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。
3. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableArray消息方法直接转交给这个成员处理。
4. 再找不到,调用setValue:forUndefinedKey:。
3.3 搜索无序集合成员,如:NSSet。
mutableSetValueForKey:搜索方式如下:
1. 搜索add<Key>Object:、remove<Key>Object:或者add<Key>:、remove<Key>:格式的方法,如果至少一个insert方法和至少一个remove方法找到,那么返回一个可以响应NSMutableSet所有方法的代理集合。那么发送给这个代理集合的NSMutableSet消息方法,以add<Key>Object:、remove<Key>Object:、add<Key>:、remove<Key>:组合的形式调用。还有两个可选实现的接口:intersect<Key>、set<Key>:。
2. 如果reciever是ManagedObejct,那么就不会继续搜索了。
3. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set<Key>:方法。也就是说,mutableSetValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。
4. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableSet消息方法直接转交给这个成员处理。
5. 再找不到,调用setValue:forUndefinedKey:。
1.4 KVC还提供了下面的功能
4.1 值的正确性核查
KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
实现核查方法为如下格式:validate<Key>:error:如:
[cpp] view plain copy
在CODE上查看代码片派生到我的代码片
-(BOOL)validateName:(id *)ioValue error:(NSError **)outError
{
// The name must not be nil, and must be at least two characters long.
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {
if (outError != NULL) {
NSString *errorString = NSLocalizedStringFromTable(
@"A Person's name must be at least two characters long", @"Person",
@"validation: too short name error");
NSDictionary *userInfoDict =
[NSDictionary dictionaryWithObject:errorString
forKey:NSLocalizedDescriptionKey];
*outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
code:PERSON_INVALID_NAME_CODE
userInfo:userInfoDict] autorelease];
}
return NO;
}
return YES;
}
调用核查方法:
validateValue:forKey:error:,默认实现会搜索 validate<Key>:error:格式的核查方法,找到则调用,未找到默认返回YES。
注意其中的内存管理问题。
1.5 集合操作
集合操作通过对valueForKeyPath:传递参数来使用,一定要用在集合(如:array)上,否则产生运行时刻错误。其格式如下:
Left keypath部分:需要操作对象路径。
Collectionoperator部分:通过@符号确定使用的集合操作。
Rightkey path部分:需要进行集合操作的属性。
5.1 数据操作
@avg:平均值
@count:总数
@max:最大
@min:最小
@sum:总数
确保操作的属性为数字类型,否则运行时刻错误。
5.2 对象操作
针对数组的情况
@distinctUnionOfObjects:返回指定属性去重后的值的数组
@unionOfObjects:返回指定属性的值的数组,不去重
属性的值不能为空,否则产生异常。
5.3 数组操作
针对数组的数组情况
@distinctUnionOfArrays:返回指定属性去重后的值的数组
@unionOfArrays:返回指定属性的值的数组,不去重
@distinctUnionOfSets:同上,只是返回值为NSSet
1.6 效率问题
相比直接访问KVC的效率会稍低一点,所以只有当你非常需要它提供的可扩展性时才使用它。
1.7代码实现以上知识:
1)直接赋值
使用KVC 可以对对象的某个属性进行赋值。如下面的代码:
假定现在我们有一个Person 类,类中包含两个属性:一个是只读的name 属性,一个是Number类型的age属性。
@interface Person : NSObject
@property(nonatomic,copy,readonly)NSString* name;
@property(nonatomic,assign)NSNumber *age;
@end
当我们定义了属性的时候,系统就为我们自动的生成了setter 和getter 方法。我们可以通过setter 和getter方法,或读取或写入数值。当然我们也可以用KVC 的方式进行读写数据。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Person *person=[[Person alloc] init];
[person setValue:@"20" forKey:@"age"];
[person setValue:@"张依依" forKey:@"name"];
NSLog(@"person 的名字是%@",person.name);
NSLog(@"person 的年领是%@",[person valueForKey:@"age"]);
}
@end
总结: 只读的属性怎么可以赋值? 还有age属性明明是NSNumber类型的,怎么可以把字符串赋给它?!没错,这就是我想说
的,KVC 不但能够赋值,而且还能破坏只读的特性。当然这只是我们需要注意的一个细节,更重要的是KVC 有自动装箱
(自动类型转换)的功能,我们不需要去转换类型了。由于开发过程中数据领域是字符串的天下,所以这个自动装箱的
功能的确是极好的。
5.赋值语句 setValue:forKey: 是给对象当前的属性赋值,而 setValue:forKeyPath: 是按照对象的层级关系为其中
的属性赋值。 forKeyPath可以替代forKey,但是forKey不能替代forKeyPath。
KVC中的方法:
2.1 获取值
valueForKey:,传入NSString属性的名字。
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。
2.2 修改值
setValue:forKey:
setValue:forKeyPath: m
setValue:forUndefinedKey:
setNilValueForKey: 当对非类对象属性设置nil时,调用,默认抛出异常。
2.3 一对多关系成员的情况
mutableArrayValueForKey:有序一对多关系成员 NSArray
mutableSetValueForKey:无序一对多关系成员 NSSet
1.3 KVC的实现细节
搜索Setter、Getter方法,这一部分比较重要,能让你了解到KVC调用之后,到底是怎样获取和设置类成员值的。
3.1 搜索简单的成员 如:基本类型成员,单个对象类型成员:NSInteger,NSString*成员。
a. setValue:forKey的搜索方式:
1. 首先搜索set<Key>:方法
如果成员用@property,@synthsize处理,因为@synthsize告诉编译器自动生成set<Key>:格式的setter方法,
所以这种情况下会直接搜索到。注意:这里的<Key>是指成员名,而且首字母大写。下同。
2. 上面的setter方法没有找到,那么类方法accessInstanceVariablesDirectly返回YES(注:这是NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)。
那么按_<key>,_is<Key>,<key>,is<key>的顺序搜索成员名。
3. 如果找到就去设置成员的值,如果没有就去调用setValue:forUndefinedKey:。
b. valueForKey:的搜索方式:
1. 首先按get<Key>、<key>、is<Key>的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。
2. 上面的getter没有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。
如果countOf<Key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。
3. 还没查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。
如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:组合的形式调用。
4. 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,_is<Key>,<key>,is<key>的顺序直接搜索成员名。
5. 再没查到,调用valueForUndefinedKey:。
3.2 查找有序集合成员,比如NSMutableArray
mutableArrayValueForKey:搜索方式如下:
1. 搜索insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexes、remove<Key>AtIndexes:格式的方法。
如果至少一个insert方法和至少一个remove方法找到,那么同样返回一个可以响应NSMutableArray所有方法的代理集合。那么发送给这个代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:atIndexes、remove<Key>AtIndexes:组合的形式调用。还有两个可选实现的接口:replaceObjectIn<Key>AtIndex:withObject:、replace<Key>AtIndexes:with<Key>:。
2. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set<Key>:方法。
也就是说,mutableArrayValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。
3. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableArray消息方法直接转交给这个成员处理。
4. 再找不到,调用setValue:forUndefinedKey:。
3.3 搜索无序集合成员,如:NSSet。
mutableSetValueForKey:搜索方式如下:
1. 搜索add<Key>Object:、remove<Key>Object:或者add<Key>:、remove<Key>:格式的方法,如果至少一个insert方法和至少一个remove方法找到,那么返回一个可以响应NSMutableSet所有方法的代理集合。那么发送给这个代理集合的NSMutableSet消息方法,以add<Key>Object:、remove<Key>Object:、add<Key>:、remove<Key>:组合的形式调用。还有两个可选实现的接口:intersect<Key>、set<Key>:。
2. 如果reciever是ManagedObejct,那么就不会继续搜索了。
3. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set<Key>:方法。也就是说,mutableSetValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。
4. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableSet消息方法直接转交给这个成员处理。
5. 再找不到,调用setValue:forUndefinedKey:。
1.4 KVC还提供了下面的功能
4.1 值的正确性核查
KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
实现核查方法为如下格式:validate<Key>:error:如:
[cpp] view plain copy
在CODE上查看代码片派生到我的代码片
-(BOOL)validateName:(id *)ioValue error:(NSError **)outError
{
// The name must not be nil, and must be at least two characters long.
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {
if (outError != NULL) {
NSString *errorString = NSLocalizedStringFromTable(
@"A Person's name must be at least two characters long", @"Person",
@"validation: too short name error");
NSDictionary *userInfoDict =
[NSDictionary dictionaryWithObject:errorString
forKey:NSLocalizedDescriptionKey];
*outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
code:PERSON_INVALID_NAME_CODE
userInfo:userInfoDict] autorelease];
}
return NO;
}
return YES;
}
调用核查方法:
validateValue:forKey:error:,默认实现会搜索 validate<Key>:error:格式的核查方法,找到则调用,未找到默认返回YES。
注意其中的内存管理问题。
1.5 集合操作
集合操作通过对valueForKeyPath:传递参数来使用,一定要用在集合(如:array)上,否则产生运行时刻错误。其格式如下:
Left keypath部分:需要操作对象路径。
Collectionoperator部分:通过@符号确定使用的集合操作。
Rightkey path部分:需要进行集合操作的属性。
5.1 数据操作
@avg:平均值
@count:总数
@max:最大
@min:最小
@sum:总数
确保操作的属性为数字类型,否则运行时刻错误。
5.2 对象操作
针对数组的情况
@distinctUnionOfObjects:返回指定属性去重后的值的数组
@unionOfObjects:返回指定属性的值的数组,不去重
属性的值不能为空,否则产生异常。
5.3 数组操作
针对数组的数组情况
@distinctUnionOfArrays:返回指定属性去重后的值的数组
@unionOfArrays:返回指定属性的值的数组,不去重
@distinctUnionOfSets:同上,只是返回值为NSSet
1.6 效率问题
相比直接访问KVC的效率会稍低一点,所以只有当你非常需要它提供的可扩展性时才使用它。
1.7代码实现以上知识:
1)直接赋值
使用KVC 可以对对象的某个属性进行赋值。如下面的代码:
假定现在我们有一个Person 类,类中包含两个属性:一个是只读的name 属性,一个是Number类型的age属性。
@interface Person : NSObject
@property(nonatomic,copy,readonly)NSString* name;
@property(nonatomic,assign)NSNumber *age;
@end
当我们定义了属性的时候,系统就为我们自动的生成了setter 和getter 方法。我们可以通过setter 和getter方法,或读取或写入数值。当然我们也可以用KVC 的方式进行读写数据。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Person *person=[[Person alloc] init];
[person setValue:@"20" forKey:@"age"];
[person setValue:@"张依依" forKey:@"name"];
NSLog(@"person 的名字是%@",person.name);
NSLog(@"person 的年领是%@",[person valueForKey:@"age"]);
}
@end
总结: 只读的属性怎么可以赋值? 还有age属性明明是NSNumber类型的,怎么可以把字符串赋给它?!没错,这就是我想说
的,KVC 不但能够赋值,而且还能破坏只读的特性。当然这只是我们需要注意的一个细节,更重要的是KVC 有自动装箱
(自动类型转换)的功能,我们不需要去转换类型了。由于开发过程中数据领域是字符串的天下,所以这个自动装箱的
功能的确是极好的。
2)支持键值路径
什么叫支持键值路径?说白了就是支持嵌套。假如现在有一个书籍类,类中包含了书籍的名称name。
书籍可以被Person所拥有(就是可以作为person的属性)
@interface Book : NSObject
@property(nonatomic,copy)NSString* name;
@end
那么我们就可以这样来用
Person *person=[[Person alloc] init];
Book *myBook=[[Book alloc] init];
person.book=myBook;
[person setValue:@"程序员摊煎饼指南" forKeyPath:@"book.name"];
NSLog(@"%@",[person valueForKeyPath:@"book.name"]);
这里的key直接使用点局分开就好了,注意一下:这里使用的时keyPath,
当然在 “ 1)属性赋值” 中我们也可以使用keyPath,只不过再不必要的情况下使用keyPath会浪费性能而已。
1.路径
除了通过键设值或取值外, 键/值编码还支持指定路径设值或取值,像文件系统一样, 用“ . ”号隔开:
[book setValue:@"比尔" forKeyPath:@"author.name"];
NSNumber *price=[book valueForKeyPath:@"relativeBooks.price"]
2.数组的整体操作
如果向 NSArray 请求一个键值,它实际上会查询数组中的每个对象来查找这个键值, 然后将查询结果打包到另一
个数组中并返回给你:
// 获取 Student 中所有 Book 的 name
NSArray *names = [student.books valueForKeyPath:@"name"]; 或者
NSArray *names = [student valueForKeyPath:@"books.name"];
//注意:不能在键路径中为数组添加索引,比如 @"books[0].name"
3)支持操作符
KVC的简单运算
//count
NSString *count = [book valueForKeyPath:@"relativeBooks.@count"];
NSLog(@"count : %@", count);
//sum
NSString *sum = [book valueForKeyPath:@"relativeBooks.@sum._price"];
NSLog(@"sum : %@", sum);
//avg
NSString *avg = [book valueForKeyPath:@"relativeBooks.@avg._price"];
NSLog(@"avg : %@", avg);
//min
NSString *min = [book valueForKeyPath:@"relativeBooks.@min._price"];
NSLog(@"min : %@", min);
//max
NSString *max = [book valueForKeyPath:@"relativeBooks.@max._price"];
NSLog(@"max : %@", max);
4)错误拦截
对于我们前端程序员来说,后端程序员有时也是一个troubleMaker。他总是给你传递一些很奇怪的东西。比如给你传递一个id 属性,或者什么都不给你传。如果有这样一个json文件 {“id”:"1"}。这是逼着我们把id作为数据模型的一个属性的节奏啊!!老夫不愿意啊!尽管作为属性也不会报错。屈服?还是抗争?这是一个问题。但是好在前辈们已经给了我们答案。假如我们有一个Model类,类中的whoCare属性就是本应命名为id 的属性。我们还写了一个字典转模型的初始化方法。
@interface Model : NSObject
@property(nonatomic,strong)id whoCare;
-(instancetype)initWithDict:(NSDictionary *)dict;
@end
那么我们可以在.m文件中重写 -(void)setValue:(id)value forUndefinedKey:(NSString *)key 方法。这个方法会在字典转模型时,系统找不到同名的属性时调用。所以我们可以再这个方法中进行错误拦截,并进行赋值操作,这样就不会报错了。
什么叫支持键值路径?说白了就是支持嵌套。假如现在有一个书籍类,类中包含了书籍的名称name。
书籍可以被Person所拥有(就是可以作为person的属性)
@interface Book : NSObject
@property(nonatomic,copy)NSString* name;
@end
那么我们就可以这样来用
Person *person=[[Person alloc] init];
Book *myBook=[[Book alloc] init];
person.book=myBook;
[person setValue:@"程序员摊煎饼指南" forKeyPath:@"book.name"];
NSLog(@"%@",[person valueForKeyPath:@"book.name"]);
这里的key直接使用点局分开就好了,注意一下:这里使用的时keyPath,
当然在 “ 1)属性赋值” 中我们也可以使用keyPath,只不过再不必要的情况下使用keyPath会浪费性能而已。
1.路径
除了通过键设值或取值外, 键/值编码还支持指定路径设值或取值,像文件系统一样, 用“ . ”号隔开:
[book setValue:@"比尔" forKeyPath:@"author.name"];
NSNumber *price=[book valueForKeyPath:@"relativeBooks.price"]
2.数组的整体操作
如果向 NSArray 请求一个键值,它实际上会查询数组中的每个对象来查找这个键值, 然后将查询结果打包到另一
个数组中并返回给你:
// 获取 Student 中所有 Book 的 name
NSArray *names = [student.books valueForKeyPath:@"name"]; 或者
NSArray *names = [student valueForKeyPath:@"books.name"];
//注意:不能在键路径中为数组添加索引,比如 @"books[0].name"
3)支持操作符
KVC的简单运算
//count
NSString *count = [book valueForKeyPath:@"relativeBooks.@count"];
NSLog(@"count : %@", count);
//sum
NSString *sum = [book valueForKeyPath:@"relativeBooks.@sum._price"];
NSLog(@"sum : %@", sum);
//avg
NSString *avg = [book valueForKeyPath:@"relativeBooks.@avg._price"];
NSLog(@"avg : %@", avg);
//min
NSString *min = [book valueForKeyPath:@"relativeBooks.@min._price"];
NSLog(@"min : %@", min);
//max
NSString *max = [book valueForKeyPath:@"relativeBooks.@max._price"];
NSLog(@"max : %@", max);
4)错误拦截
对于我们前端程序员来说,后端程序员有时也是一个troubleMaker。他总是给你传递一些很奇怪的东西。比如给你传递一个id 属性,或者什么都不给你传。如果有这样一个json文件 {“id”:"1"}。这是逼着我们把id作为数据模型的一个属性的节奏啊!!老夫不愿意啊!尽管作为属性也不会报错。屈服?还是抗争?这是一个问题。但是好在前辈们已经给了我们答案。假如我们有一个Model类,类中的whoCare属性就是本应命名为id 的属性。我们还写了一个字典转模型的初始化方法。
@interface Model : NSObject
@property(nonatomic,strong)id whoCare;
-(instancetype)initWithDict:(NSDictionary *)dict;
@end
那么我们可以在.m文件中重写 -(void)setValue:(id)value forUndefinedKey:(NSString *)key 方法。这个方法会在字典转模型时,系统找不到同名的属性时调用。所以我们可以再这个方法中进行错误拦截,并进行赋值操作,这样就不会报错了。