作者:wangzz
原文地址:KVC/KVO原理详解及编程指南
本文只转载KVC部分讲解,KVO部分可到原博客查看。
本人在阅读过程中也进行了一些勘误修改。
前言
1、本文基本不讲KVC/KVO的用法,只结合网上的资料说说对这种技术的理解。
2、由于KVO内容较少,而且是以KVC为基础实现的,本文将着重介绍KVC部分。
一、简介
KVC/KVO是观察者模式的一种实现,在Cocoa中是以被万物之源NSObject类实现的NSKeyValueCoding/NSKeyValueObserving
非正式协议的形式被定义为基础框架的一部分。从协议的角度来说,KVC/KVO本质上是定义了一套让我们去遵守和实现的方法。
当然,KVC/KVO实现的根本是Objective-C的动态性和runtime,这在后文的原理部分会有详述。
另外,KVC/KVO机制离不开访问器方法的实现,这在后文中也有解释。
1、KVC简介
全称是Key-value coding
,翻译成键值编码。顾名思义,在某种程度上跟map的关系匪浅。它提供了一种使用字符串而不是访问器方法去访问一个对象实例变量的机制。
2、KVO简介
全称是Key-value observing
,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。在MVC大行其道的Cocoa中,KVO机制很适合实现model和controller类之间的通讯。
二、KVC相关技术
1、Key和Key Path
KVC定义了一种按名称访问对象属性的机制,支持这种访问的主要方法是:
|
|
前边两个方法用到的Key较容易理解,就是要访问的属性名称对应的字符串。
后面两个方法用到的KeyPath是一个被点操作符隔开的用于访问对象的指定属性的字符串序列。比如KeyPath address.street将会访问消息接收对象所包含的address属性中包含的一个street属性。
其实KeyPath说白了就是我们平时使用点操作访问某个对象的属性时所写的那个字符串。
2、点语法和KVC
在实现了访问器方法的类中,使用点语法和KVC访问对象其实差别不大,二者可以任意混用。但是没有访问起方法的类中,点语法无法使用,这时KVC就有优势了。( 原因见第三部分的第一节:KVC如何访问属性值。 )
3、一对多关系(To-Many)中的集合访问器方法
我们平时大部分使用的属性都是一对一关系(To-One),比如Person类中的name属性,每个人只有一个名字。但也有一对多的关系,比如Person中有一个friendsName属性,这是个集合(在Objective-C中可以是NSArray,NSSet等),保存的是一个人的所有朋友的名字。
当操作一对多的属性中的内容时,我们有两种选择:
①间接操作
先通过KVC方法取到集合属性,然后通过集合属性操作集合中的元素。
②直接操作
苹果为我们提供了一些方法模板,我们可以以规定的格式实现这些方法来达到访问集合属性中元素的目的。
有序集合对应方法如下:
|
|
无序集合对应方法如下:
|
|
不过这些方法除非是很有需求,否则个人认为没有实现的必要,间接法也不是很麻烦,基本能满足需求了。值得指出的是,苹果甚至都没有让这些方法以哪怕是非正式协议的形式出现,而只是在编程指南中提了一下。
4、键值验证(Key-Value Validation)
KVC提供了验证Key对应的Value是否可用的方法:
|
|
该方法默认的实现是调用一个如下格式的方法:
|
|
比如属性name对应的方法为:
|
|
这样就给了我们一次纠错的机会。
需要指出的是,KVC是不会自动调用键值验证方法的,就是说我们需要手动验证。但是有些技术,比如CoreData会自动调用。
5、KVC对数值和结构体型属性的支持
一套机制如果不支持数值和结构体型的数据,那么它的实用性就会大大折扣。幸运的是KVC中苹果对这方面的支持做的很好。KVC可以自动的将数值或结构体型的数据打包或解包成NSNumber或NSValue对象,以达到适配的目的。
举个例子,Person类有个NSInteger类型的age属性
①修改值
我们通过KVC技术使用如下方式设置age属性的值:
|
|
我们赋给age的是一个NSNumber对象,KVC会自动的将NSNumber对象转换成NSInteger对象,然后再调用相应的访问器方法设置age的值。
②获取值
同样,以如下方式获取age属性值:
|
|
这时,会以NSNumber的形式返回age的值。
需要说明的是,什么时候返回的是NSNumber,什么时候返回的是NSValue?
③使用NSNumber封装
可以使用NSNumber的数据类型有:
|
|
总之就是一些常见的数值型数据。
④使用NSValue封装
NSValue主要用于处理结构体型的数据,它本身提供了如下几种结构的支持:
|
|
只有有限的6种而已!那对于其它自定义的结构体怎么办?别担心,任何结构体都是可以转化成NSValue对象的,具体实现方法参见我之前的一篇文章:NSNumber和NSValue对基础C数据类型的对象化封装
6、集合运算符(Collection Operators)
集合运算符是一个特殊的Key Path,可以作为参数传递给valueForKeyPath:
方法,注意只能是这个方法,如果传给了valueForKey:方法保证你程序崩溃。
运算符是一个以@开头的特殊字符串,格式如下图所示:
①简单集合运算符
简单集合运算符共有@avg
,@count
,@max
,@min
,@sum
5种,都表示啥不用我说了吧,目前还不支持自定义。
有一个集合类的对象:transactions,它存储了一个个的Transaction类的实例,该类有三个属性:payee,amount,date。下面以此为例说明如何使用这些运算符:
要获取amount的平均值可以这样:
|
|
要获取transactions集合中元素数目可以这样:
|
|
需要之处的是,@count
是这些集合运算符中比较特殊的一个,因为它没有右路经,原因很容易理解。
②对象运算符
比集合运算符稍微复杂,能以数组的方式返回指定的内容,一共有两种:
@distinctUnionOfObjects
@unionOfObjects
它们的返回值都是NSArray,区别是前者返回的元素都是唯一的,是去重以后的结果;后者返回的元素是全集。
用法如下:
|
|
前者会将收款人的姓名去除重复的以后返回,后者直接返回所有收款人的姓名。
③Array和Set操作符
这种情况更复杂了,说的是集合中包含集合的情况,我们执行了如下的一段代码:
|
|
得到了一个包含集合的集合:arrayOfTransactionsArray
这时如果我们想操作arrayOfTransactionsArray中包含的集合中的元素时,可以使用如下三个运算符:
@distinctUnionOfArrays
@unionOfArrays
@distinctUnionOfSets
前两个针对的集合是Arrays,后一个针对的集合是Sets。因为Sets中的元素本身就是唯一的,所以没有对应的@unionOfSets
运算符。
它们的用法举例如下:
|
|
再一次感谢您花费时间阅读这篇文章!
微博: @Danny_吕昌辉
博客: SuperDanny