1.KVC是啥? 键值编码(key value coding)是一种可以通过字符串的名字(key)来访问类属性的机制。区别于通过调用Setter、Getter来访问属性的方法。
2.啥用? 通常我们要访问一个类中属性的值时,可以使用点语法(如 people.name)。但当你想访问私有属性或成员变量时,点语法就没用了。而有了 KVC 问题就迎刃而解。
NSKeyValueCoding.h
中有个 NSObject 的分类:NSObject(NSKeyValueCoding)
,其中定义了以下方法:
1 2 3 4 - (id) valueForKey:(NSString *) key; - (id) valueForKeyPath:(NSString *) keyPath; - (void) setValue:(nullable id) value forKey:(NSString *) key; - (void) setValue:(nullable id) value forKeyPath:(NSString *) keyPath;
其中,-valueForKey: 与 -valueForKeyPath:在一般的属性访问时,效果是一样的。但要访问类似下面案例1中 Student.index 这种子属性时,就只能使用后者,使用前者编译时没问题但运行时会崩溃。
#示例1:
1 2 3 4 5 6 7 8 9 10 @interface Student : NSObject@property (nonatomic,assign) int index;@end @interface People : NSObject { NSString *name ; Student *student ; } @end
1 2 3 4 5 6 7 8 9 10 11 #import "People.h" @implementation Student @end @interface People ()@property (nonatomic , copy ) NSString *eMail;@end @implementation People @end
调用示例:
1 2 3 4 5 6 7 8 9 10 11 People *people = [People new];Student *student = [Student new]; [people setValue:@"David" forKey:@"name" ]; [people setValue:@"email@" forKey:@"eMail" ]; [people setValue:student forKey:@"student" ]; [people setValue:@(101 ) forKeyPath:@"student.index" ];NSString *name = [people valueForKey:@"name" ]; int index = [[people valueForKeyPath:@"student.index" ] intValue];NSLog (@"++++name:%@,index:%d" ,name,index);
3.底层的原理? 在NSKeyValueCoding.h
对KVC
的实现过程有详细的解释。
3.1.valueForKey:
在方法接收者的类中先按照 getKey,key,isKey 的顺序查找 getter 方法,找到直接调用。如果是 BOOL,int 等内建值类型,会做 NSNumber 类型转化。
没有找到的话,如果方法接收者的 accessInstanceVariablesDirectly 属性返回YES(默认返回YES),那么依次搜索符合_key,_isKey,key,isKey 格式的成员变量,找到后返回它的值。
再没找到的话,会调用 -valueForUndefinedKey,在没被重写的情况下,此方法默认抛出 NSUndefinedKeyException 异常。
3.2.setValue:forKey:
首先在方法接收者所属的类中搜索 setKey: 格式的方法并检测其参数类型。如果参数类型符合则直接调用该方法;如果参数不是对象指针类型但值为nil,则会调用 -setNilValueForKey: 并抛出异常;
第一步中没有找到格式相符的方法的话,如果 accessInstanceVariablesDirectly 属性返回 YES。那么就去依次查询符合_key,_isKey,key,isKey 格式的成员变量,找到后给它赋值。
如果仍没找到符合的成员变量,则调用 setValue:forUnderfinedKey: 并抛出 NSUndefinedKeyException 异常。
由上述过程可以得知:valueForKey
会调用属性的getter
,setValue:forKey:
会调用属性的setter
函数。
#示例2:
1 2 3 4 5 6 7 8 @interface Model : NSObject@property (nonatomic, strong) NSString *_modelString;@end @interface People : NSObject@property (nonatomic, strong) NSString *stringA;@property (nonatomic, strong) Model *modelA;@end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #import "People.h" @implementation Model - (void )set_modelString:(NSString *)_modelString { __modelString = _modelString; NSLog (@"++++执行 setter _modelString" ); } - (void )setModelString:(NSString *)modelString { NSLog (@"++++执行 setter modelString" ); } - (void )setNoExist1:(NSString *)noExist { NSLog (@"++++执行 setter noExist1 " ); }@end @implementation People - (void )setStringA:(NSString *)stringA { _stringA = stringA; NSLog (@"++++执行 setter stringA" ); } - (instancetype )init { if (self = [super init]) { self .modelA = [[Model alloc] init]; } return self ; }@end
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - (void )kvcTest { People *apeo = [[People alloc] init]; apeo.stringA = @"stringA setter" ; ①[apeo setValue:@"stringA KVC" forKey:@"stringA" ]; ②[apeo setValue:@"_stringA KVC" forKey:@"_stringA" ]; NSLog (@"++++apeo.stringA 值: %@" , apeo.stringA); NSLog (@"++++++++++++++++++++++++++++++" ); ③[apeo setValue:@"_modelString kvc" forKeyPath:@"modelA._modelString" ]; ④[apeo setValue:@"modelString kvc" forKeyPath:@"modelA.modelString" ]; ⑤[apeo setValue:@"__modelString kvc" forKeyPath:@"modelA.__modelString" ]; ⑥[apeo setValue:@"noExist1" forKeyPath:@"modelA.noExist1" ]; NSLog (@"++++apeo.modelA._modelString 值: %@" , apeo.modelA._modelString); NSLog (@"++++++++++++++++++++++++++++++" ); ⑦NSString *s1 = [apeo valueForKeyPath:@"modelA._modelString" ]; ⑧NSString *s2 = [apeo valueForKeyPath:@"modelA.modelString" ]; ⑨NSString *s3 = [apeo valueForKeyPath:@"modelA.__modelString" ]; NSLog (@"++++s1:%@ s2:%@ s3:%@" ,s1,s2,s3); }
输出日志:
1 2 3 4 5 6 7 8 9 10 +++ +执行 setter stringA+++ +执行 setter stringA++++apeo.stringA 值: _stringA KVC ++++++++++++++++++++++++++++++ +++ +执行 setter _modelString ++++执行 setter modelString ++++执行 setter noExist1 ++++apeo.modelA._modelString 值: __modelString kvc ++++++++++++++++++++++++++++++ ++++s1:__modelString kvc s2:__modelString kvc s3:__ modelString kvc
日志显示:①~⑨全部执行成功;其中①③④⑥ 执行了setter方法,⑦⑧执行了getter方法,②⑤⑨直接访问的实例变量。
3.3.小结 当我们使用id objectA = objectB.value2
时是否代表 objectB 有一个 value2属性
呢?实际上不一定,例如object.class
,NSObject 中 并没有class
属性,只有一个class
方法。
OC的点语法中,.
表示调用方法,即.
操作只是去寻找一个名称匹配参数匹配的方法。我们习以为常的属性调用只是因为属性刚好有getter
,setter
方法符合要求而已。如果.
表达式在=
左边,则该属性的setter
方法被调用;如果.
表达式在=
的右边,则属性的getter
方法被调用。
#KVC与集合运算符 多数情况下,keyPath
被用来读取对象中子对象的某个属性,如示例1中的[people valueForKeyPath:@"student.index"]
。除此之外,苹果还将此方法用在了集合中,用以实现某些常见的集合运算,如求最大值、最小值、求和等,这就是我们要介绍的集合运算符
在KVC
中的应用。
When you send a key-value coding compliant object the valueForKeyPath: message, you can embed a collection operator in the key path. A collection operator is one of a small list of keywords preceded by an at sign (@) that specifies an operation that the getter should perform to manipulate the data in some way before returning it. The default implementation of valueForKeyPath: provided by NSObject implements this behavior.
我们可以在keyPath
中加入集合运算符
,它们以@
开头,用来指定对数据的某种操作,最终返回处理后的结果。具体格式为:
格式说明:
左边:将要执行运算的集合;
中间:运算符;
右边:集合中对象的属性;
如果是数组调用了valueForKeyPath
,则左边部分可以省略;
运算符为数组的count
时右边的部分可以忽略,其他运算的右边不能为空。
#示例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @interface Card : NSObject @property (nonatomic ) int cardNumber; @property (nonatomic ) float money; @end #import "Card.h" @interface User : NSObject @property (nonatomic ) NSInteger age; @property (nonatomic ) NSArray *cardArr; @end - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { Card *card1 = [Card new]; card1.cardNumber = 100 ; card1.money = 1000 ; Card *card2 = [Card new]; card2.cardNumber = 101 ; card2.money = 2000 ; User *user = [User new]; user.age = 20 ; user.cardArr = @[card1,card2]; User *user2 = [User new]; user2.age = 30 ; NSArray *userArr = @[user,user2]; int min = [[user valueForKeyPath:@"cardArr.@min.money" ] intValue]; int max = [[user valueForKeyPath:@"cardArr.@max.money" ] intValue]; int sum = [[user valueForKeyPath:@"cardArr.@sum.money" ] intValue]; int avg = [[user valueForKeyPath:@"cardArr.@avg.money" ] intValue]; int count = [[userArr valueForKeyPath:@"cardArr.@count" ] intValue]; int count2 = [[userArr valueForKeyPath:@"@count" ] intValue]; int maxAge = [[userArr valueForKeyPath:@"@max.age" ] intValue]; return YES ; }
#示例2:数组中数字运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (void )kvcToSumArr { NSArray *array = @[@"1" ,@"2" , @"3" ]; int max = [[array valueForKeyPath:@"@max.intValue" ] intValue]; int min = [[array valueForKeyPath:@"@min.intValue" ] intValue]; int sum = [[array valueForKeyPath:@"@sum.intValue" ] intValue]; float avg = [[array valueForKeyPath:@"@avg.floatValue" ] floatValue]; NSLog(@"+++Max:%d,Min:%d,Sum:%d,Avg:%f" ,max ,min ,sum ,avg); }
相关参考:
#©Apple-KVC Using Collection Operators
#©掘金