KVC的定义
键值编码是由NSKeyValueCoding非正式协议启用的一种机制,对象采用这种机制来提供对其属性的间接访问。当对象是键值编码兼容的对象时,可以通过简洁,统一的消息传递接口通过字符串参数来访问其属性。这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。
访问对象事属性
KVC 设值
-
setValue:forKey:
将相对于接收消息的对象的指定键的值设置为给定值。setValue:forKey:自动实现代表标量和结构的自动包装NSNumber和NSValue对象的默认实现,并将其分配给属性。如果给非对象类型设置为nil,会走setNilValueForKey:
并抛出异常。
如果指定的键对应于接收setter调用的对象所不具有的属性,则该对象将向自身发送setValue:forUndefinedKey:
消息。默认实现setValue:forUndefinedKey:
会引发一个NSUndefinedKeyException。但是子类可以重写setValue:forUndefinedKey:
以自定义方式处理请求。
setValue forKey:
的搜索模式:
1、先查找访问器方法,按顺序依次查set<Key>:
、_set<Key>:
、setIs<Key>:
,如果存在某个方法,使用value调用该方法来设置属性值,查找完成。否则,执行第2步。
2、没有找到上述方法,如果类方法accessInstanceVariablesDirectly返回YES,依次查找实例变量_<key>
,_is<Key>
,<key>
,is<Key>
。如果找到,则直接使用value设置实例变量的值并完成操作。
3、如果访问器和实例变量都未找到,调用setValue:forUndefinedKey:
。默认情况下会引发异常,但是的子类NSObject可能提供特定于键的行为。
看图可能更好理解。
-
setValue:forKeyPath:
在相对于接收器的指定键路径处设置给定值。密钥路径序列中不符合特定键的键值编码的任何对象都会收到一条setValue:forUndefinedKey:
消息。 -
setValuesForKeysWithDictionary:
使用字典键识别属性,以指定字典中的值设置接收器的属性。默认实现setValue:forKey:为每个键值对调用,nil并NSNull根据需要替换对象。
KVC 取值
-
valueForKey:
返回由key参数命名的属性的值。如果根据描述的规则找不到由关键字命名的属性,则该对象向自身发送一条valueForUndefinedKey:
消息。默认实现valueForUndefinedKey:
会引发一个NSUndefinedKeyException,但是子类可以重写valueForUndefinedKey
,处理一些异常。
valueForKey:
的搜索模式:
1、依次搜索访问器方法get<Key>
,<key>
,is<Key>
,_<key>
。如果找到其中一个,调用它并继续执行步骤5。否则,继续下一步。
2、如果未找到访问器方法,搜索countOf<Key>
和objectIn<Key>AtIndex:
,生成并返回NSKeyValueArray数组。(与NSArray该类定义的原始方法<key>AtIndexes:
相对应)和(与该NSArray方法相对应)相匹配的方法objectsAtIndexes:
。
如果找到其中的第一个,再找到其他两个中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并将其返回。否则,请继续执行步骤代理对象随后将任何NSArray接收到的一些组合的消息countOf<Key>
,objectIn<Key>AtIndex:
和<key>AtIndexes:
消息给键-值编码创建它兼容的对象。如果原始对象还实现了名称类似的可选方法get<Key>:range:
,则代理对象也会在适当时使用该方法。
3、查找名为countOf<Key>
、enumeratorOf<Key>
和memberOf<Key>:
(对应于NSSet类定义的原始方法)的三个方法。如果三个方法全部存在,则创建一个能够响应NSSet类所有方法的集合代理对象,并返回该集合代理对象,查找完成。否则执行第4步。
以后在操作该集合代理对象时,集合代理对象会将其接收的任何NSSet消息转换为countOf<Key>
、enumeratorOf<Key>
和memberOf<Key>
:消息的某种组合并发送给valueForKey:消息的接收对象。实际上,集合代理对象与兼容键值编码的对象一起工作,使得兼容键值编码的对象的集合属性的行为就像该属性是NSSet一样,即使它并不是。
4、如果访问器方法和集合访问方法组都未找到,并且accessInstanceVariablesDirectly
返回YES,则按顺序依次查找实例变量_<key>
、_is<Key>
、<key>
或者is<Key>
。如果找到一个,则直接获取实例变量的值并执行第5步。否则,执行第6步。
5、如果检索到的属性值是对象指针,直接返回结果,查找完成。
如果该值是支持的NSNumber,将其存储在NSNumber实例中并返回该实例。
如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象。
6、如果上诉都未找到,会调用valueForUndefinedKey:
抛出异常,但是子类可以重写该方法处理一些异常。
搜索的流程图如下:
-
valueForKeyPath:
返回相对于接收者的指定密钥路径的值。密钥路径序列中不符合特定键的键值编码的任何对象(即,默认实现valueForKey:无法找到访问器方法)均会接收到valueForUndefinedKey:
消息。 -
dictionaryWithValuesForKeys:
返回相对于接收者的键数组的值。该方法调用valueForKey:数组中的每个键。返回的NSDictionary值包含数组中所有键的值。
特殊说明:集合对象,如NSArray,NSSet和NSDictionary,不能包含nil的值。而是nil使用NSNull对象表示值。NSNull提供一个表示nil对象属性值的实例,默认实现dictionaryWithValuesForKeys:
和相关的自动setValuesForKeysWithDictionary:
转换NSNull(在dictionary参数中)和nil(在storage属性中)。
访问集合属性
-
mutableArrayValueForKey:
输入一个key,返回一个可变数组代理,该代理提供对给定键指定的有序对多关系的读写访问。
内部实现:mutableArrayValueForKey:
内部动态创建了NSMutableArray的子类NSKeyValueMutableArray,然后指针混写指向子类,并返回子类。常与KVO搭配使用,比如:观察某个对象的arr属性,实际观察的是arr的子类NSKeyValueMutableArray。
mutableArrayValueForKeyPath:
输入一个key路径,返回一个可变数组,该数组提供对给定键路径指定的有序对多关系的读写访问mutableSetValueForKey:
输入一个key,返回一个可变集合代理,该代理提供对给定键指定的无序对多关系的读写访问。mutableSetValueForKeyPath:
输入一个key路径,返回一个可变集合,该集合提供对给定键路径指定的无序对多关系的读写访问。mutableOrderedSetValueForKey:
输入一个key,返回一个可变的有序集合,该集合提供对给定键指定的唯一有序多对关系的读写访问。mutableOrderedSetValueForKeyPath:
输入一个key路径,返回一个可变的有序集合,该集合提供对给定键路径指定的唯一有序多对关系的读写访问。
使用集合运算符
①集合运算符
KVC提供了5中集合运算符,分别是:@avg, @count , @max , @min ,@sum。
示例:
KVCPerson *person1 = [KVCPerson new];
person1.dog.age = 1;
KVCPerson *person2 = [KVCPerson new];
person2.dog.age = 1;
KVCPerson *person3 = [KVCPerson new];
person3.dog.age = 3;
KVCPerson *person4 = [KVCPerson new];
person4.dog.age = 4;
NSArray *arr = @[person1, person2, person3, person4];
NSNumber *sum = [arr valueForKeyPath:@"@sum.dog.age"];
NSLog(@"sum:%.f", sum.floatValue);
NSNumber *avg = [arr valueForKeyPath:@"@avg.dog.age"];
NSLog(@"avg:%.f", avg.floatValue);
NSNumber *count = [arr valueForKeyPath:@"@count"];
NSLog(@"count:%.f", count.floatValue);
NSNumber *min = [arr valueForKeyPath:@"@min.dog.age"];
NSLog(@"min:%.f", min.floatValue);
NSNumber *max = [arr valueForKeyPath:@"@max.dog.age"];
NSLog(@"max:%.f", max.floatValue);
打印结果:
2021-03-26 00:23:48.619763+0800 Project[4156:128149] sum:9
2021-03-26 00:23:48.620122+0800 Project[4156:128149] avg:2
2021-03-26 00:23:48.620306+0800 Project[4156:128149] count:4
2021-03-26 00:23:48.620562+0800 Project[4156:128149] min:1
2021-03-26 00:23:48.620726+0800 Project[4156:128149] max:4
②数组运算符
@distinctUnionOfObjects
和@unionOfObjects
,获取对象属性的值,并以数组的方式返回。
区别:@distinctUnionOfObjects
会去重,而@unionOfObjects
不会去重。
KVCPerson *person1 = [KVCPerson new];
person1.dog.age = 1;
KVCPerson *person2 = [KVCPerson new];
person2.dog.age = 1;
KVCPerson *person3 = [KVCPerson new];
person3.dog.age = 3;
KVCPerson *person4 = [KVCPerson new];
person4.dog.age = 4;
打印结果:
2021-03-26 00:29:23.011315+0800 Project[4208:131986] distinctUnionOfObjects
2021-03-26 00:29:23.011539+0800 Project[4208:131986] 3
2021-03-26 00:29:23.011702+0800 Project[4208:131986] 1
2021-03-26 00:29:23.011846+0800 Project[4208:131986] 4
2021-03-26 00:29:23.011975+0800 Project[4208:131986] unionOfObjects
2021-03-26 00:29:23.012168+0800 Project[4208:131986] 1
2021-03-26 00:29:23.012308+0800 Project[4208:131986] 1
2021-03-26 00:29:23.012445+0800 Project[4208:131986] 3
2021-03-26 00:29:23.012579+0800 Project[4208:131986] 4
③嵌套运算符
@distinctUnionOfArrays
当指定@distinctUnionOfArrays运算符时,valueForKeyPath:创建并返回一个数组,该数组包含与右键路径指定的属性相对应的所有集合的组合的不同对象。
@unionOfArrays
当指定@unionOfArrays运算符时,将valueForKeyPath:创建并返回一个数组,该数组包含与右键路径指定的属性相对应的所有集合的组合的所有对象,而不会删除重复项。
@distinctUnionOfSets
当指定@distinctUnionOfSets运算符时,将valueForKeyPath:创建并返回一个NSSet对象,该对象包含与右键路径指定的属性相对应的所有集合的组合中的不同对象。
该运算符的行为类似于@distinctUnionOfArrays,只是它期望NSSet包含NSSet对象实例,而不是NSArray实例的NSArray实例。同样,它返回一个NSSet实例。假设示例数据已存储在集合中而不是数组中,则示例调用和结果与所示相同@distinctUnionOfArrays。
运算符这块相对来说,集合运算符和数组运算符用的多一点,嵌套运算符用的相对较少。
总结
日常开发中,灵活的运用KVC可以更加方便的实现某些功能,并且使代码更加简洁。