关于KVC和KVO,我之前的总结文章有写过,但是实际上我在平日工作里,KVC和KVO使用的相对较少,不是KVC和KVO的功能不够强大,这实际上和项目的架构有比较大的关系,以前的我对于KVC和KVO的使用也是趋于表面,没有探究其内部真正的实现原理和进阶用法,这次总结正好给了我很好的学习机会,在此深入的总结一下KVC和KVO吧。
KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。
KVC在iOS中的定义
Objective-C中KVC的定义是对NSObject的扩展来实现的。所以对于所有继承了NSObject在类型,都可以使用KVC,下面是KVC最为重要的四个方法
1 | - (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值 |
一般来讲,Obj-C 对象中都会有一些属性。如代码所示
1 |
|
上面的Person对象所拥有的两个属性,以 KVC的角度来看,就是Person对象的name,address这两个属性分别有一个Value对应他们的Key值。
- Key 是一个字符串类型。
- Value 可以为任何类型。
KVC 为存取值提供了两个最基础的方法。
1 | Person *man = [Person new]; |
KVC 为了便于使用还提供了另外两个方法。
假设我们之前创建的这个对象有一个配偶,配偶也是一个Person对象,此时我们想在man这里读出woman的name属性
可以这样操作
1 | Person *woman = [Person new]; |
简单对比一下
1 | // 结果一样的,但是用 KeyPath 更简单 |
KVC寻找Key值过程
KVC在某种程度上提供了访问器的替代方案,不过只要有可能,KVC也是在访问器方法的帮助下工作。KVC按照以下顺序寻找Key值。
1.赋值
当程序调用
1 | - (void)setValue:(nullable id)value forKey:(NSString *)key; |
1.优先寻找访问器方法
程序会优先调用setKey的属性值方法,代码直接通过Setter方法完成设置。这里的key值指的是成员变量名,Key值首字母大写要符合Setter和Getter方法的命名规则。
2.寻找_key
如果没有找到setKey的访问器方法,KVC机制会检查
1 | + (BOOL)accessInstanceVariablesDirectly |
的返回值是否为NO,此方法默认返回的是YES。如果开发者重写了该方法让这个返回值为NO时,接下来KVC会直接调用
1 | - (void)setValue:(id)value forUndefinedKey:(NSString *)key |
这个时候如果你不做其他操作,就要报出异常了,所以一般人都不会这么做。
接下来KVC机制会搜索该类里面有没有_key的成员变量,无论你是在声明文件中定义,还是在实现文件中定义,也无论使用了什么样的属性修饰符,只要存在着_key命名的变量,KVC都可以对该成员变量赋值。
3.寻找_isKey
如果该类即没有setKey:的访问器方法,也没有_key成员变量,KVC机制会搜索_isKey的成员变量。
4.寻找Key和isKey
和上面一样,如果该类即没有setKey:的访问器方法,也没有_key和_isKey成员变量,KVC机制再会继续搜索key和isKey的成员变量。再给它们赋值。
如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUNdefinedKey:方法,默认是抛出异常。
如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set<Key>:属性名时,会直接用setValue:forUNdefinedKey:方法。
2.取值
当程序调用
1 | - (nullable id)valueForKey:(NSString *)key; |
1.优先查找访问器的方法
首先按getKey、key、isKey的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。
2.有序集合中查找
上面的getter没有找到,查找countOfKey、objectInKeyAtIndex:、KeyAtIndexes格式的方法。
如果countOfKey和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合。发送给这个代理集合的NSArray消息方法,就会以countOfKey、objectInKeyAtIndex:、KeyAtIndexes这几个方法组合的形式调用。还有一个可选的 getKey:range: 方法。
3.无序集合中查找
还没查到,那么查找countOfKey、enumeratorOfKey、memberOfKey:格式的方法。
如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合。发送给这个代理集合的NSSet消息方法,就会以countOfKey、enumeratorOfKey、memberOfKey:组合的形式调用。
4.搜索成员变量名
还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_key,_isKey,key,iskey的顺序直接搜索成员名。
5.报出异常
再找不到,调用ValueForUndefinedKey:,默认报出异常
针对集合的KVC
我们上面讲的KVC是一对一关系,比如Person类中的name属性。但也有一对多的关系,比如Person中有一个friendsName属性,保存的是一个人的所有朋友的名字,这时候就需要集合来处理了。
对于集合类的处理,我们有两种选择
1.通过KVC将集合类先取出,然后在针对集合进行处理
2.采用KVC提供的模板方法
有序集合
这里面的Key,就是被监听的属性名称
1 | -countOfKey |
无序集合
1 | - countOfKey |
KVC对基本数据类型和结构体的支持
1.对基本数据类型会以 NSNumber 进行包装
1 | + (NSNumber *)numberWithChar:(char)value; |
2.对结构体会以 NSValue 进行包装
1 | + (NSValue *)valueWithCGPoint:(CGPoint)point; |
所有的结构体都支持以NSValue进行封装
KVC中的集合运算符

集合运算符是一个特殊的KeyPath,可以作为参数传递给valueForKeyPath:方法
1.简单的集合运算符
简单的集合运算符有以下几个 @avg,@count,@max,@min,@sum5
2.对象运算符
对象运算符有@distinctUnionOfObjects,@unionOfObjects,这两个运算符返回的对象都是NSArray。
1.@distinctUnionOfObjects会将集合在剔除重复对象之后返回
2.@unionOfObjects会直接返回所有对象
NSKeyValueCoding其他方法
1 | + (BOOL)accessInstanceVariablesDirectly; |
没想到光一个KVC就写了这么多的内容,而越深入写就越觉得自己写的不过是皮毛,接下来再说说KVO吧。