许多编程技术都基于间接机制,包括整个面向对象编程领域。键值编码也是一种间接机制,这种机制不属于Objective-C语言的特性,而是Cocoa提供的一种特性。
我们可以通过直接调用方法、属性的点表示法或设置实例变量来直接更改对象状态。键值编码是一种间接更改对象状态的方式,称其为KVC,其实现方法是使用字符串表示要更改的对象状态。
一些更加高级的Cocoa特性,比如CoreData和CocoaBindings,也在基础机制中使用了KVC功能。
1 入门项目
2 KVC简介
键/值编码中的基本调用是 -valueForKey:和-setValue:forKey:方法。你可以向对象发送消息,并传递你想要访问的属性名称的键作为参数。
我们可以通过如下方法访问对象的属性:
image.png
image.png
valueForKey:的功能非常强大,它可以找到相关属性的值并将其返回。
valueForKey:会首先查找以参数名命名(格式为-key或-isKey)的getter方法。对于以上前两个调用,valueForKey:会先寻找-name和-make方法。如果没有这样的getter方法,它将会在对象内寻找名称格式为_key或key的实例变量。
非常重要的一点:-valueForKey在Objective-C运行时中使用元数据打开对象并进入其中查找需要的信息。在C或C++语言中不能执行这种操作。通过使用KVC,没有相关getter方法也能获取对象值,不需要通过对象指针来直接访问实例变量。
对于KVC, Cocoa会自动装箱和开箱标量值。也就是说,当使用setValueForKey时,它会自动将标量值(int、float和struct)放入NSNumber或NSValue中;当使用-setValueForKey时,它自动将标量值从这些对象中取出。仅KVC具有这种自动装箱功能,常规方法调用和属性语法不具备该功能。
除了检索值外,还可以使用-setValue:forKey:方法依据名称设置值。
以上方法的工作方式与-valueForKey:相同。它先查找名称的setter方法,然后调用它并传递参数。如果不存在setter方法,它将在类中寻找名为key或_key的实例变量,然后为它赋值。
编译器和苹果公司都以下划线开头的形式保存实例变量名称,所以,你最好遵守这条规则,不要在其他地方使用下划线。
如果你想设置一个标量值,在调用-setValue:forKey:方法之前需要将它们包装起来,也就是装箱到对象中,而对于KVC的赋值方法,Cocoa会自动开箱先取标量值,再赋值。
3.键路径
表示键路径:可以在不同的变量名称之间用圆点分开。
image.png
image.png
这些键路径的深度是任意的,具体取决于对象图(object graph, 可以表示对象之间的关系)的复杂度,可以使用诸如car.interior.airconditioner.fan.velocity这样的键路径。在某种程度上,使用键路径比使用一系列嵌套方法调用更容易访问到对象。
4.整体操作
关于KVC非常棒的一点是,如果使用某个键值来访问一个NSArray数组,它实际上会查询相应数组中的每个对象,然后将查询结果打包到另一个数组中并返回给你。这种方法也同样适用于通过键路径访问的位于对象中的数组。
在KVC中,通常认为对象的NSArray具有一对多的关系。如果键路径中含有一个数组属性,则该路径的其余部分将被发送给数组中的每个对象。(第二句话后一部分的理解: NSArray数组实现valueForKeyPath:的方法是循环遍历它的内容并向每个对象发送消息。因此NSArray向每个在自身之中的对象发送了参数以剩余部分作为键路径的valueForKeyPath:消息,结果就将数组中每个对象对应的属性值放到数组中返回)。
键路径的缺点: 不幸的是,不能在键路径中索引对象的数组,比如说获取对象的数组中的第一个被包含对象的属性值。
一对一关系,一般对象的复合都是一对一关系。
4.1 休息一下
懒加载也可以称之为惰性初始化,仅在需要时才去创建它。这是编程技术的一种思想。
4.2 快速运算
键路径不仅能引用对象值,还可以引用一些运算符来进行一些运算,例如能获取一组值的平均值或返回这组值中的最小值和最大值。
image.png
image.png
注意:上述代码中的@count, 其中@符号意味着后面将进行一些运算。对编译器来说,@"blah"是一个字符串,而@interface用于声明类。此处的@count用于通知KVC机制计算键路径左侧值的对象总数。而@sum、@avg、@min、@max、@distinctUnionOfObjects等运算符将键路径分成两部分。第一部分可以看成一对多关系的键路径,另一部分可以看成任何包含一对多关系的键路径。它被当作用于关系中每个对象的键路径。(获取左侧指定的集合,对该集合中的每个对象使用右侧的键路径,然后将结果转换为一个集合。名称中的union指一组对象的并集,distince用于删除重复内容。KVC还支持多种运算符,可供自己探索,不过遗憾的是,你无法添加自己的运算符。
KVC的功能非常强大,可以非常轻松地处理集合类,似乎完全可以取代对象的存取方法和其他代码编写。但是KVC有以下两个缺点:
① KVC需要解析字符串来计算你需要的答案,因此速度比较慢。
② 编译器无法对键路径进行错误检查。当你尝试使用它时,一旦出现键路径错误,程序直接crash掉。
个人意见:一般情况下,通过对象的存取方法和其他代码实现不了相应的功能时,再考虑使用KVC。
-
批处理
KVC包含两个调用, 一个可以批量获取,另一个可以批量更改。
image.png
image.png
注意:集合类对象中不能包含nil值,因为nil是集合类对象的终止符号,若包含,则会引起非法参数异常程序crash。在OC中,我们一般使用[NSNull null]表示nil值。
-
nil仍然可用
虽然nil不可在集合类对象中使用,但仍然可在KVC赋值中使用,而对于属性标量值设置为nil程序crash的问题有办法解决。
image.png
image.png
注意:如果得到了一个意外之料的键,我们可以调用超类方法。
一般来说,除非你有某些特殊的原因,比如不想执行某个操作,否则应该总是在重写的代码中调用超类的方法。 -
处理未定义的键
使用KVC,处理未定义的键,需要重写相关的方法。如果KVC机制无法找到处理方式,会退回并询问该类如何处理。默认的实现会取消操作,但是我们可以更改默认的行为。
image.png
image.png
image.png
注意:
① <null>为后端返回数据,或者控制台打印,<null>是一种[NSNull null]对象。所以,当后端返回<null>,前端可用[NSNull null]来进行判断;(null) 是一个真正的nil值,所以,同样,前端可用nil值来判断后端返回的(null)。
② 对于字典,使用KVC的赋值方法(setValue:forKey:),调用者可以直接传入nil值。而如果为字典的setObject:forKey:提供nil值,它将会给出警告信息,程序crash。其实,对于后台返回的数据或者用户输入的数据,在提交字典参数请求服务端时都必须进行非空判断,最好的办法是在程序中每一处进行非空判断,另外一种办法:可以尝试使用runtime解决。
③ 如果在字典中对setValue:forKey:方法传入nil值,可能会把对应键的值从字典中删除。
小结:
① KVC通过查找setter和getter方法来使用单个键设置值或者获取值。如果KVC无法找到任何方法,将直接进入对象并更改值。
② 键路径:它们是由点分割的键,用于在对象的网络中指定路径。也许这些键路径看起来很像访问属性,但实际上它们是两种完全不同的机制。可以将各种运算符嵌入到键路径中,以使KVC实现其他功能。
③ 通过重写系统方法可以定制个别行为。
网友评论