KVC(Key Value Coding)
是定义在NSKeyValueCoding.h
文件中的一个非正式协议。KVC
提供了一种可以间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。
常用API
设值与取值
常用到的设值与取值的几种方式:
// 通过Key来取值
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 通过KeyPath(也称路由)来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
KeyPath
使用示例入
[myAcount setValue:@"xiaoxiao" forKeyPath:@"model.name"]; // 可以通过 . 的形式获取到更深层的属性
NSLog(@"%@",[myAcount valueForKeyPath:@"model.name"]);
字典操作
- (void)dictionaryTest{
NSDictionary* dict = @{
@"name":@"xiaoxiao",
@"age":@18,
};
PeasonModel *p = [[PeasonModel alloc] init];
// 字典转模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"%@",p);
// 键数组转模型到字典
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
NSLog(@"%@",dic);
}
其他常用方法
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//如果Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
设值&取值过程
1.设值
常用设值方法就是setValue:forKey
,其底层原理是怎么样的呢?开发过程中,我们可能曾经试着Jump to Definition
去查看底层实现,然后只有注释,Foundation
闭源的。想要窥探其底层,可能需要其他手段:1.通过苹果官方文档;2.Github搜相关的工程;3.通过Hopper反汇编,通过伪代码去理解;
官网Key-Value Coding
在官网中找到了以下关于setValue:forkey
的解释
执行流程是:
- 1.先寻找是否有
set<Key> or _set<Key>
,如果找到,就直接设置属性value
.(key是指成员变量名,首字符大小写需要符合KVC的命名规范)); - 2.如果没找到,就判断
accessInstanceVariablesDirectly
返回YES,就按照以下顺序寻找一个实例变量,名称类似_<key>,_is<Key>,<key>,或者is<Key>
,找到就直接赋值; - 3.如果返回为NO,则表明
setter
方法 或者 实例变量都没有找到,系统会执行该对象的setValue:forUndefinedKey:
方法,默认会抛出NSUndefinedKeyException类型的异常。
整理得到流程图更方便理解
2.取值
当然也就找到了valueForKey:
的解释
流程是:
- 1.先找getter方法,按照顺序依次查找:
get<Key> -> <key> -> is<Key> -> _<key>
,如果找到执行第5,否者执行第2; - 2.按顺序次没找着,就会寻找
countOf <Key>和objectIn <Key> AtIndex :和<key> AtIndexes :
, 如果找到了countOf <Key>
或其他两个中的一个,则会创建一个响应所有NSArray方法的集合代理对象,并返回该对象,即NSKeyValueArray
,是NSArray的子类。否则执行第3.
代理对象随后将接收到的所有NSArray消息转换为countOf<Key>
,objectIn<Key> AtIndex:
和<key>AtIndexes:
消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为get<Key>:range:之类的可选方法,则代理对象也将在适当时使用该方法; - 3.上面方法没找到,就会查找countOf <Key>,enumeratorOf<Key>和memberOf<Key>这三个方法;
如果找到所有的三个方法,则会创建一个响应所有NSSet
方法的集合代理对象并将其返回。否则,请继续执行步骤4。
此代理对象随后将其收到的所有NSSet消息转换为countOf <Key>,enumeratorOf <Key>和memberOf <Key>:
消息的某种组合,以创建它的对象。实际上,代理对象与与键值编码兼容的对象一起工作,使基础属性的行为就好像它是NSSet一样,即使它不是NSSet那样。 - 4.如果没有找到方法组,并且接收方的类方法
accessInstanceVariablesDirectly
返回YES,则按顺序搜索名为_ <key>,_ is <Key>,<key>或is <Key>
的实例变量。如果找到,请直接获取实例变量的值,然后继续执行步骤5。否则,请继续执行步骤6。 - 5.如果搜索到的属性值的类型为指针,则直接返回结果;
如果该值是NSNumber
支持的标量类型,则将其存储在NSNumber
实例中并返回它。
如果结果是NSNumber
不支持的标量类型,则转换为NSValue
对象并返回该对象。
如果所有其他方法均失败,则调用valueForUndefinedKey:
。默认情况下,这会引发一个异常NSUndefinedKeyException
。
整理得到流程图:
KVC应用场景
1、动态设值和取值
常用通过setValue:forKey:
和valueForKey:
, 或者setValue:forKeyPath:
和valueForKeyPath:
2、通过KVC访问和修改私有变量
在外部定义的对象是无法直接访问私有属性的,但是可以通过KVC修改和访问任何私有属性3、多值操作(Model和字典互转)
// 字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
// 模型转字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- 4、修改一些系统空间的内部属性
很多UI控件都是在其内部由多个UI控件组合而成,使用KVC可以访问到底层的API,常用的就是自定义Tabbar
、个性化UITextField
中的placeHolderText
。