iOS KVC学习
关于键值编码
key-value coding 翻译为 键值编码 , 简称KVC.
键值编码是一种由NSKeyValueCoding
非正式协议启用的机制,对象采用该机制提供对其属性的间接访问。当对象符合键值编码时,其属性可通过字符串参数通过简洁,统一的消息传递接口寻址。这种间接访问机制补充了实例变量及其相关访问器方法提供的直接访问。
您通常使用访问器方法来访问对象的属性。get访问器(或getter)返回属性的值。set访问器(或setter)设置属性的值。在Objective-C中,您还可以直接访问属性的基础实例变量。以任何这些方式访问对象属性都很简单,但需要调用特定于属性的方法或变量名称。随着属性列表的增长或变化,访问这些属性的代码也必须如此。相反,符合键值编码的对象提供了一个简单的消息传递接口,该接口在其所有属性中都是一致的。
键值编码是一个基本概念,是许多其他Cocoa技术的基础,例如键值观察(key-value observing),Cocoa绑定(Cocoa bindings),Core Data和AppleScript-ability。在某些情况下,键值编码还有助于简化代码。
使用键值编码兼容对象
对象通常在NSObject
(直接或间接)继承时采用键值编码,它们都采用NSKeyValueCoding
协议并为基本方法提供默认实现。这样的对象通过紧凑的消息传递接口使其他对象能够执行以下操作:
- 访问对象属性。 该协议指定方法,例如getter
valueForKey:
和settersetValue:forKey:
,用于通过名称或键访问对象属性,参数为字符串。这些和相关方法的默认实现使用键来定位基础数据并与其交互,如Accessing Object Properties。 - 操纵集合属性。 访问方法的默认实现和对象的集合属性(如
NSArray
对象)一样,也和任何其他属性一样。此外,如果对象定义属性的集合访问器方法,则它允许对集合内容进行键值访问。这通常比直接访问更有效,并允许您通过标准化界面使用自定义集合对象,如Accessing Collection Properties。 - 在集合对象上调用集合运算符。 在符合键值编码的对象中访问集合属性时,可以将集合运算符插入到键字符串中,如Using Collection Operators。集合运算符根据默认的
NSKeyValueCoding
getter实现对集合执行操作,然后返回集合的新的过滤版本或表示集合的某些特征的单个值。
//@sum:值的总和
//@avg:平均值
//@count:总个数
//@max:最大值
//@min:最小值
Person * p1 = [[Person alloc]init];
Person * p2 = [[Person alloc]init];
Person * p3 = [[Person alloc]init];
p1->age = 18;
p2->age = 30;
p3->age = 56;
NSArray * array = @[p1,p2,p3];
NSNumber* sum = [array valueForKeyPath:@"@sum.age"];
NSNumber* avg = [array valueForKeyPath:@"@avg.age"];
NSNumber* count = [array valueForKeyPath:@"@count"];
NSNumber* min = [array valueForKeyPath:@"@min.age"];
NSNumber* max = [array valueForKeyPath:@"@max.age"];
NSLog(@"sum:%@, avg:%@, count:%@, min:%@, max:%@",sum,avg,count,min,max);
//打印结果:sum:104, avg:34.66666666666666, count:3, min:18, max:56
- 访问非对象属性。 协议默认实现检测非对象属性,包括标量和结构体,并自动将它们包装和解包为协议接口上使用的对象,如Representing Non-Object Values。此外,该协议声明了一种方法,该方法允许兼容对象
nil
通过键值编码接口在非对象属性上设置值时为该情况提供合适的作用。 - key path访问属性。 如果具有符合键值编码的对象层次结构,则可以使用基于key path的方法调用,使用单个调用在层次结构内深入查看,获取或设置值。
采用对象的键值编码
为了使您自己的对象键值编码符合要求,您需要确保它们采用NSKeyValueCoding
非正式协议并实现相应的方法,例如作为valueForKey:
通用getter和setValue:forKey:
通用setter。幸运的是,如上所述,NSObject
采用此协议并为这些和其他基本方法提供默认实现。因此,如果您从NSObject
(或其许多子类中的任何一个)派生对象,那么大部分工作已经完成。
为了使默认方法完成其工作,您需要确保对象的访问器方法和实例变量遵循某些明确定义的模式。这允许默认实现找到对象的属性以响应键值编码消息。然后,您可以选择通过提供验证方法和处理某些特殊情况来扩展和自定义键值编码。
上面为翻译部分引用自 作者:codeTao 来源
基础Getter的搜索模式
-
在实例中搜索找到的第一个名称为
get<Key>
、<key>
、is<Key>
、 或 的访问器方法_<key>
,按该顺序。如果找到,则调用它并使用结果继续执行步骤 5。否则继续下一步。 -
如果没有找到简单的访问器方法,则在实例中搜索名称与模式
countOf<Key>
和objectIn<Key>AtIndex:
(对应于NSArray
类定义的原始方法)和<key>AtIndexes:
(对应于NSArray
方法objectsAtIndexes:
)的方法。如果找到这些中的第一个和其他两个中的至少一个,则创建一个响应所有
NSArray
方法的集合代理对象并返回该对象。否则,继续执行步骤 3。代理对象随后将任何
NSArray
接收到的一些组合的消息countOf<Key>
,objectIn<Key>AtIndex:
和<key>AtIndexes:
消息给键-值编码创建它兼容的对象。如果原始对象还实现了一个可选的方法,其名称类似于get<Key>:range:
,则代理对象也会在适当的时候使用它。实际上,与键值编码兼容的对象一起工作的代理对象允许底层属性表现得好像它是NSArray
,即使它不是。 -
如果没有找到简单的访问方法或阵列访问方法组,寻找一个三重的方法命名
countOf<Key>
,enumeratorOf<Key>
和memberOf<Key>:
(对应于由所定义的原始的方法NSSet
类)。如果找到所有三个方法,则创建一个响应所有
NSSet
方法的集合代理对象并返回该对象。否则,继续执行步骤 4。此代理对象随后将任何
NSSet
接收到的一些组合信息countOf<Key>
,enumeratorOf<Key>
和memberOf<Key>:
消息以创建它的对象。实际上,与键值编码兼容的对象一起工作的代理对象允许底层属性表现得好像它是NSSet
,即使它不是。 -
如果发现收集的访问方法没有简单的存取方法或者组,如果接收器的类方法
accessInstanceVariablesDirectly
返回YES
,搜索名为实例变量_<key>
,_is<Key>
,<key>
,或者is<Key>
,按照这个顺序。如果找到,直接获取实例变量的值并进行步骤5,否则进行步骤6。 -
如果检索到的属性值是一个对象指针,只需返回结果即可。
如果该值是 支持的标量类型
NSNumber
,则将其存储在一个NSNumber
实例中并返回该实例。如果结果是 NSNumber 不支持的标量类型,则转换为
NSValue
对象并返回。 -
如果所有其他方法都失败,请调用
valueForUndefinedKey:
. 默认情况下,这会引发异常,但 的子类NSObject
可能会提供特定于键的行为。
基本 Setter 的搜索模式
的默认实现setValue:forKey:
,给定的key
和value
参数作为输入,尝试设置命名属性key
到value
(或,对于非对象属性,的展开的版本 value
,如在代表非对象值)的物体内接收到呼叫,使用以下程序:
- 按顺序 查找名为
set<Key>:
or的第一个访问器_set<Key>
以及setIs<Key>:
。如果找到,则使用输入值(或根据需要展开的值)调用它并完成。 - 如果没有找到简单的访问,如果类方法
accessInstanceVariablesDirectly
返回YES
,寻找一个实例变量与名称类似_<key>
,_is<Key>
,<key>
,或者is<Key>
,按照这个顺序。如果找到,直接使用输入值(或解包值)设置变量并完成。 - 找不到访问器或实例变量后,调用
setValue:forUndefinedKey:
. 默认情况下,这会引发异常,但NSObject
的子类可能会提供特定于键的行为。
Person * p1 = [[Person alloc]init];
[p1 setValue:@"Kowaii" forKey:@"name"];
NSLog(@"value = %@",[p1 valueForKey:@"_name"]);
// NSLog(@"Person %@-%@-%@-%@",p1->_name,p1->name,p1->_isName,p1->isName);
NSLog(@"Person %@--%@-%@",p1->name,p1->_isName,p1->isName);
//person代码
//- (void)setName:(NSString*)name{
// NSLog(@"func %s name = %@",__FUNCTION__,name);
//}
//
//- (void)_setName:(NSString*)name{
// NSLog(@"func %s name = %@",__FUNCTION__,name);
//}
- (void)setIsName:(NSString*)name{
NSLog(@"func %s name = %@",__FUNCTION__,name);
}
此处发现setIsName 会打印,官方文档只说了set<Key>:
以及 _set<Key>
# objectForKey、valueforKey和valueForKeyPath的区别
-
valueForKey:
andvalueAtKeyPath:
都是NSKeyValueCoding
非正式协议规定的方法,都是在NSObject里面默认实现的方法objectForKey:
是字典的方法 -
valueForKey:
只能一层索引, 而valueAtKeyPath:
可以层层索引,例如
NSDictionary *dict1 = @{@"dic1":@{@"dic2":@{@"name":@"lisi",@"info":@{@"age":@"111"}}}};
id res = [dict1 valueForKeyPath:@"[dict1.dict2.name]"];
3.valueForKey:
它可以遍历多个对象。所以你可以有一个带有一堆属性的根对象,这些属性中的每一个都是另一个带有另一组属性的对象,等等,并且使用一个键路径你可以在该数据结构的叶子上检索一个值,而不是为自己遍历一个又一个对象。可以使用 操纵集合属性、 在集合对象上调用集合运算符。 等等独特的骚操作,还有想骚的可自行去探索
NSArray *arr = @[@{@"city":@"beijing",
@"person":@{
@"name":@"库细"
}
},
@{
@"city":@"shenzhen"
}];
NSLog(@"%@",[arr valueForKeyPath:@"city"]);
//打印 (beijing,chengdu)
转载自:https://juejin.cn/post/6989124486008143885