关于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
吧。