前言
这几天在面试过程中,常常被问到KVC
,但是总觉得自己回答的不够全面细致,所以特此整理一份KVC
的学习笔记。
正文
KVC介绍
KVC
全名是Key-value coding
,键值编码。也就是说是在iOS开发过程中,允许开发者通过Key
直接访问对象的属性,或者给对象的属性赋值。这也是由于ObjC
的语言的特性(运行时)根本不必进行任何操作就可以对属性进行动态读写。
KVC
的操作方法由NSKeyValueCoding
协议提供的,而NSObject
就实现了这个协议,也就是说在ObjC
中几乎所有的对象都支持KVC
操作。
常用到的方法:
/// 动态读取
- (nullable id)valueForKey:(NSString *)key; // 通过Key取值
- (nullable id)valueForKeyPath:(NSString *)keyPath; // 通过KeyPath取值,通常使用场景都是对更深层的对象进行操作
/// 动态设置
- (void)setValue:(nullable id)value forKey:(NSString *)key; // 通过Key设置值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // 通过KeyPath设置值,通常使用场景都是对更深层的对象进行操作
KVC的实现原理
设值
设值过程
- 按照
setKey:
、_setKey:
顺序查找方法,如果找到了就执行; - 如果未找到的话对应的
set
方法就会查看accessInstanceVariablesDirectly
方法返回值,如果该方法返回值是true
就会按照_key
,_isKey
,key
,isKey
的顺序查找成员变量,如果找到了的话就会直接赋值,如果没有找到对应的成员变量的话,就会抛出NSUndefinedKeyException
异常; - 如果
accessInstanceVariablesDirectly
返回值是false
的话,就会抛出NSUndefinedKeyException
异常。
设值总结
KVC
的设置值其实本质就是调用属性的setter
方法;
setter
方法的优先级分别是setKey
、_setKey
、setIsKey
的优先级;
accessInstanceVariablesDirectly
方法默认值是true
,会按照_key
,_isKey
,key
,isKey
的顺序查找成员变量。
取值原理
取值过程
- 按照
getKey
,key
,isKey
的顺序查找getter
方法,找到了直接调用(如果值是bool
或者int
等值类型的时候,会将其包装成NSNumber
对象); - 如果
getter
方法未找到,会继续查找countOfKey
,objectInKeyAtindex
,KeyAtindexes
格式的方法,如果countOfKey
和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray
所有方法的代理集合的NSArray
消息方法; - 如果没有找到,会继续查找
countOfKey
、enumeratorOfKey
、memberOfKey
方法,如果这三个方法都找到了,那么就返回一个可以响应NSSet
所有方法的代理集合; - 如果没有找到,就会查看
accessInstanceVariablesDirectly
方法返回值,如果该方法返回值是true
就会按照_key
,_isKey
,key
,isKey
的顺讯查找成员变量,如果找到了的话就会直接赋值,如果没有找到对应的成员变量的话,就会抛出NSUndefinedKeyException
异常; - 如果
accessInstanceVariablesDirectly
返回值是false
的话,就会抛出NSUndefinedKeyException
异常。
取值总结
在取值过程中通过getKey:、key:、isKey:取到的值为直接返回的值,所以本质上是按先后顺序调用了这三个setter方法,如果没有,则会询问accessInstanceVariablesDirectly
方法能否直接取成员变量,若返回true
,则会按顺序取_key
、_isKey
、key
、isKey
的值。
KVC的应用场景
KVC的主要应用场景:
- 动态的取值和设值
- 用KVC来访问和修改私有变量
- Model和字典转换
- 修改一些控件的内部属性
动态的取值和设值
利用KVC
动态的取值和设值是最基本的用户。
Person *p = [[Person alloc] init];
[p setValue:@"Katy" forKey:@"name"]; // 设值
[p setValue:@(27) forKey:@"age"];// 取值
NSLog(@"%@的年龄是%@",[p valueForKey:@"name"],[p valueForKey:@"age"]);// 取值
用KVC来访问和修改私有变量
[textField setValue:[UIColor xxx] forKeyPath:@"_placeholderLabel.textColor"];
不过iOS13
一些私有的KVC
会引发奔溃。这些好像跟系统版本无关,与Xcode
版本有关,Xcode11
编译会奔溃。
所以通过KVC来访问和修改私有变量需慎重。
Model和字典转换
如果在做网络请求的时候解析数据不是使用第三方,而是自己写解析方法的话,这种就比较常见,就不再次赘述了。
修改一些控件的内部属性
一般会使用runtime
结合KVC
修改一些空间的内部属性。
KVC常见异常
NSUndefinedKeyException
当根据KVC
搜索规则进行搜索key
或者keyPath
的时候,没有找到对应的值,会调用对应的异常方法。如果是异常的默认实现时会抛出一个NSUndefinedKeyException
的异常,应用程序会Crash
。
解决方案是重写以下方法:
- (id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
NSInvalidArgumentException
当通过KVC
给某个非对象的属性赋值为nil
时,此时KVC
会调用属性所属对象的- (void)setNilValueForKey:(NSString *)key;
方法,并抛出NSInvalidArgumentException
的异常,应用程序会Crash
。
解决方案是重写以下方法:
- (void)setNilValueForKey:(NSString *)key;
网友评论