- 对象属性值的设置和获取
- 访问集合属性值的专用API
- 使用集合操作符
- 描述非对象的值(基本数据类型和结构体等)
- 属性类型验证
- 访问器的搜索方式
对象属性值的设置和获取
KVC的通用API
setValueForKey(Path):
valueForKey(Path):一次设置、获取多个属性值
setValueForKeysWithDictionary: 对内部的每一个属性均发送setValueForKey消息
dictionaryWithValueForKeys: 根据keys数组返回字典
访问集合属性值的专用API
| 通过key访问集合 | 通过keyPath访问集合 | 被代理的类 |
|---|---|---|
| mutableArrayValueForKey: | mutableArrayValueForKeyPath: | NSMutableArray |
| mutableSetValueForKey: | mutableSetValueForKeyPath: | NSMutableSet |
| mutableOrderedSetValueForKey: | mutableOrderedSetValueForKeyPath: | NSMutableOrderedSet |
访问集合属性的值时除了可以直接使用基本valueForKey(Path):方法,还可以使用上述三类专用API进行访问。当你对集合属性进行修改(增、删、改)时,上表的三类方法可以更高效的完成任务,他们分别返回一个类似NSMutableArray、NSMutableSet和NSMutableOrderedSet的代理类的对象,可以在此对象上直接调用任何相关类的api来直接操作属性值。
使用此类api管理集合属性的高效体现在:
- 比类型为不可变集合类型属性,通过valueForKey:取值,转换并修改后,再通过setValueForKey:保存回去效率高
- 比直接将集合属性类型设置成mutable类修改高效
- 对KVO有效
示例代码:
|
|
使用集合操作符
在使用valueForKeyPath:访问集合属性时,可以在keyPath中使用集合操作符来直接操作返回的值。集合操作符使用“@”字符标识,使用集合操作符的keyPath结构如下:

如图可知:keyPath分为左keyPath、集合操作符、右keyPath三部分。
集合操作符主要分为三类:
- 聚合操作符:对集合对象进行操作,返回匹配条件的单一对象作为结果(@count除外,它没有右keyPath,且返回一个NSNumber对象)。
主要包括:@sum、@max、@min、@avg、@count等。
|
|
- 数组操作符:对集合对象进行操作,通过对右keyPath指定的值进行操作,返回特定条件的集合对象。
主要包括@unionOfObjects和@distinctUnionOfObjects,二者区别是:前者直接返回指定属性的值的数组,不过滤重复的值。
|
|
- 嵌套操作符:对于包含属性对象的集合所组成的集合(如数组内部包含的所有元素都是数组,这些子数组内部都是属性对象),嵌套操作符通过对右keyPath指定的值进行操作,返回特定条件的集合对象。
包括@unionOfArrays、@distinctUnionOfArrays和@distinctUnionOfSets,前两者对嵌套数组进行操作、第三个对集合(NSSet)进行操作。
|
|
描述非对象的值(基本数据类型和结构体等)
- 对于基本数据类型的值,使用KVC进行设置和获取时需要将值进行对NSNumber类的封包和解包。
- 对于内置的结构体,如NSPoint、NSRange、NSRect和NSSize,需要使用NSValue类对数据进行封包和解包。
- 对于自定义的结构体,使用NSValue的通用api对数据进行封包和解包。
对于自定义结构体的KVC,举例如下:
|
|
属性类型验证
KVC协议定义了一些方法用于验证属性。除了使用key或keyPath进行设置和获取值以外,还可以使用它们进行属性验证。当调用validateValue:forKey:error(或者validateValue:forKeyPath:error)方法时,协议方法的默认实现是去查找是否对key存在validate[key]:error:方法。如果没有实现此方法,则验证方法默认通过,返回YES;如果你实现了这个key的方法,则会根据此方法的验证结果来返回原验证结果。
你实现的validate[key]:error:方法根据key的值和error的指针,有三种验证实现方案:
- 判定传入的值符合要求,直接返回YES,且不修改值和错误对象。
- 判定传入的值不符合,且不对此值进行类型修正(如类型转换等)。这种情况下,方法返回NO,并且对用户传入的错误指针(如果传入了)进行赋值,包含其中的错误信息。
- 判定传入的值不符合,但是创建个符合要求的新值进行替换。这种情况下,方法返回YES,且把传入的值的指针指向此新值(一定不要直接修改传入的值,就算是个mutable类对象也不行),并且不用处理错误信息。
举例来说:
|
|
访问器的搜索方式
NSObject提供给NSKeyValueCoding协议的默认实现,是使用基于键(key)的访问器,根据一系列规则来调用对象的属性。这些协议方法使用一个键(key)参数来搜索对象实例的访问器(accessor)、实例变量和一些遵循确定命名规范的方法。了解搜索的工作原理,不仅有助于追踪KVC对象的行为,还可以帮助编写自己的兼容对象(类似KVC)。
- 基本getter的搜索模式(valueForKey:的工作模式):
- 首先依次查找访问方法set[Key]、[key]、is[Key]、_[key]。找到则跳到【5】中处理结果,否则进入下一步。
- 若没有找到访问器方法,则搜索匹配名为countOf[Key]和objectIn[Key]AtIndex:的实例方法(与NSArray中定义的原方法一致),还有[key]AtIndexes:方法(与NSArray中的objectsAtIndexes:一致)。如果找到了第一个方法和后两者之一,则创建并返回一个集合代理对象来响应所有的NSArray的方法(给代理对象发送NSArray方法时会自动组合调用以上实现的方法)。不满足则进入下一步【3】。
代理对象会把收到的所有的NSArray的消息(即API调用)转化成给KVC相关对象(即类实例对象)组合发送countOf[Key]、objectIn[Key]AtIndex:和[key]AtIndexes:消息。如果原类中额外实现了get[Key]:range:方法,此代理对象也会适时使用,以提高性能。实际上,代理对象会和原实例对象一起工作,让它看起来就像是一个NSArray对象(虽然不是)。 - 当没有找到访问器方法和数组的访问方法时,系统再继续查找一组三个名为countOf[Key]、enumeratorOf[Key]和memberOf[Key]:的实例方法(都是NSSet类中的方法)。三个方法都找到,则创建一个集合代理对象来响应所有的NSSet方法并返回。不满足则进入下一步【4】。
与NSArray的情况一样,系统会根据实现的方法生成并返回一个NSSet的代理对象,与原对象一起工作,来使其看起来像是一个NSSet对象(与上面一样,给返回的代理集合对象调用NSSet的api,你实现的countOf[Key]、enumeratorOf[Key]和memberOf[Key]:方法就会被组合调用。)。 - 访问器方法和集合访问方法组都没有找到,系统会查看类中是否实现了accessInstanceVariablesDirectly,如果该方法返回YES,则进入成员列表中进行进一步查找。依次查找名为_[key]、_is[Key]、[key]和is[Key]的成员变量。找到则跳到【5】中处理结果,否则进入下一步。
- 如果得到的属性值是对象指针,直接返回结果。如果值是一个支持NSNumber转换的基本数据类型值,则封包成NSNumber对象返回。如果是一个不支持NSNumber转换的基本数据类型值(如结构体),则封包成NSValue对象返回。
- 若以上查找均失败,则触发valueForUndefinedKey:方法,默认抛出异常。子类可以覆盖此方法。
- 基本setter的搜索模式(setValue:forKey:的工作模式):
默认实现是,给定key和value参数作为输入,试图把key设置为value(对于非对象属性,要先解包成相应值)。被调用的对象依照下面的程序进行查找:
- 依次查找名为set[Key]:或_set[Key]的访问器方法,找到后,使用输入值触发该方法并完成(依照需要先解包值)。
- 没找到访问器时,查看类方法accessInstanceVariablesDirectly,如果返回YES,依次查找成员变量中的类似_[key]、_is[Key]或is[Key]。找到,使用输入值触发该方法并完成(依照需要先解包值)。
- 以上都没找到,则触发setValue:forUndefinedKey:方法。默认抛出异常,子类可以覆盖并提供其他行为(如赋值给其他属性等)。
- 可变数组的搜索模式:
mutableArrayValueForKey:方法默认实现是输入key,返回调用对象的以key命名的属性(要求此属性是数组或可变数组类型,不可变数组无法实现步骤【1】)。此访问器的调用程序如下:
- 查找一组方法命名如insertObject:in[Key]AtIndex:和removeObjectFrom[Key]AtIndex:(与NSMutableArray中的insertObject:atIndex和removeObjectAtIndex:一致),或者名为insert[Key]:atIndexed:和removeObjectsAtIndexes:(与NSMutableArray的insertObjects:atIndexes:和removeObjectsAtIndexesL:一致)。
如果调用对象至少包含一个插入方法和一个删除方法,就返回一个可变代理对象。以后通过给此代理对象发送NSMutableArray的所有消息,都会通过组合调用insertObject:in[Key]AtIndex:、removeObjectFrom[Key]AtIndex:、insert[Key]:atIndexed:和removeObjectsAtIndexes:方法的形式来进行响应(或者可以直接将这些方法声明在.h文件中,外部类实例直接调用此方法也可以直接触发KVO机制)。
如果原类的对象还实现了可选的替换方法,如replaceObjectIn[Key]AtIndex:withObject:或replace[Key]AtIndexes:with[Key]:,代理对象也会适时调用此方法来提高效率。 如果类的实例对象没有这些可变的数组方法,就查找名字如set[Key]:的访问方法进行匹配。在这种情况下,返回的代理对象都是通过调用原始对象的set[Key]:方法来间接响应NSMutableArray的消息。
注意:
步骤【2】中的机制由于需要每次创建新的集合对象(set[Key]:,setter方法设置,而不是修改已存在的对象),导致性能要比步骤【1】中要差了不少。所以自己设计KVC相关对象时要避免这种代码。既没有找到可变数组方法,也没有找到访问器,查看类的accessInstanceVariablesDirectly方法是否返回YES,是则依次去类的成员变量中查询是否存在如_[key]或[key]。若存在,则返回一个代理对象,系统会把代理对象收到的每个NSMutableArray的消息都转发给这个成员变量,这个变量的类型需要是NSMutableArray或其子类。
- 如果上面所有的步骤都失败了,就返回一个可变的集合代理对象,作为参数,来触发setValue:forUndefinedKey:方法。默认在内部抛出NSUndefinedKeyException异常,子类可以覆盖进行其他处理。
查了好多东西,发现只有在KVO时,使用此可变容器的api进行KVC属性设置。由于调用此api返回的是可变集合对象,且是系统在运行时动态生成的,内部已经包含了KVO使用的isa指针等。当对此代理对象调用NSMutableArray方法时,会自动触发KVO机制。
其他如可变无序集合和可变有序集合的搜索模式与此类似,不再细说。