iOS底层KVC原理
Posted WeaterMr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层KVC原理相关的知识,希望对你有一定的参考价值。
概念
KVC
的全称是Key-Value Coding
,翻译成中文是 键值编码,键值编码是由NSKeyValueCoding
非正式协议启用的一种机制,对象采用该协议来间接访问其属性。既可以通过一个字符串key
来访问某个属性。这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问。
KVC 相关API
常用
- 通过key 设值/取值
- 通过keyPath (即路由)设值/取值
其他
//默认返回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;
下面的流程只是对官方文档的翻译,可以打开链接,选择中文模式,尽情玩耍!!!
KVC 设值流程
当调用setValue:forKey:
设置属性value
时,其底层的执行流程为
【第一步】首先查找是否有这三种setter方法,按照查找顺序为set<Key>
:-> _set<Key>
-> setIs<Key>
如果有其中任意一个setter方法,则直接设置属性的value(主注意:key
是指成员变量名,首字符大小写需要符合KVC
的命名规范)
如果都没有,则进入【第二步】
【第二步】:如果没有第一步中的三个简单的setter方法,则查找accessInstanceVariablesDirectly
是否返回YES
,
如果返回YES,则查找间接访问的实例变量进行赋值,查找顺序为:_<key>
-> _is<Key>
-> <key>
->is<Key>
如果找到其中任意一个实例变量,则赋值
如果都没有,则进入【第三步】
如果返回NO,则进入【第三步】
【第三步】如果setter方法 或者 实例变量都没有找到,系统会执行该对象的setValue:forUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型的异常
例子:
以[person setValue:@"newName" forKey:@"name"];
为例,可以得出以下结论:
优先通过setter
方法,进行属性设置,调用顺序是:
setName
_setName
setIsName
如果以上方法均未找到,并且accessInstanceVariablesDirectly
返回YES
,则通过成员变量进行设置,顺序是:
_name
_isName
name
isName
KVC 取值流程
当调用valueForKey:
时,其底层的执行流程如下
【第一步】首先查找getter方法,按照get<Key>
-> <key>
->is<Key>
-> _<key>
的方法顺序查找,
如果找到,则进入【第五步】
如果没有找到,则进入【第二步】
【第二步】如果第一步中的getter方法没有找到,KVC
会查找countOf <Key>
和objectIn <Key> AtIndex
:和<key> AtIndexes
:
如果找到countOf <Key>
和其他两个中的一个,则会创建一个响应所有NSArray
方法的集合代理对象,并返回该对象,即NSKeyValueArray
,是NSArray
的子类。代理对象随后将接收到的所有NSArray
消息转换为countOf<Key>
,objectIn<Key> AtIndex:
和<key>AtIndexes:
消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为get<Key>:range:
之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合KVC
的标准命名方法,包括方法签名。)
如果没有找到这三个访问数组的,请继续进入【第三步】
【第三步】如果没有找到上面的几种方法,则会同时查找countOf <Key>
,enumeratorOf<Key>
和memberOf<Key>
这三个方法
如果这三个方法都找到,则会创建一个响应所有NSSet方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet消息转换为countOf<Key>
,enumeratorOf<Key>
和memberOf<Key>:
消息的某种组合,用于创建它的对象
如果还是没有找到,则进入【第四步】
【第四步】如果还没有找到,检查类方法InstanceVariablesDirectly
是否YES
,依次搜索_<key>
,_is<Key>
,<key>
或is<Key>
的实例变量
如果搜到,直接获取实例变量的值,进入【第五步】
【第五步】根据搜索到的属性值的类型,返回不同的结果
如果是对象指针,则直接返回结果
如果是NSNumber
支持的标量类型,则将其存储在NSNumber
实例中并返回它
如果是是NSNumber
不支持的标量类型,请转换为NSValue
对象并返回该对象
【第六步】如果上面5步的方法均失败,系统会执行该对象的setValue:forUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型的异常
例子:
以[person valueForKey:@"name"];
为例
即:
getter
方法的调用顺序是:
getName
name
isName
_name
如果以上方法没有找到,accessInstanceVariablesDirectly
返回YES
,则直接返回成员变量,获取顺序依然是:
_name
_isName
name
isName
自定义KVC
思路:通过给NSObject添加分类的方式,实现自定义set和 get方法
KVC 使用场景
提示:
在某些情况下尽量不用kvc的形式访问苹果没有开放出的属性及功能,在2020开发者大会上有相关的强调,尽可能使用苹果的API,因为所用的功能都是封装在API里的,如果不通过API形式访问可能会出错,或者在版本更新的时候会报错,以及崩溃。
- 1、动态设值和取值
常用的可以通过setValue:forKey: 和 valueForKey:
也可以通过路由的方式setValue:forKeyPath: 和 valueForKeyPath:
- 2、通过
KVC
访问和修改私有变量
在日常开发中,对于类的私有属性,在外部定义的对象,是无法直接访问私有属性的,但是对于KVC
而言,一个对象没有自己的隐私,所以可以通过KVC
修改和访问任何私有属性 - 3、多值操作(
model
和字典互转)
model和字典的转换可以通过下面两个KVC的API
实现
//字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
//模型转字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- 4、修改一些系统空间的内部属性
在日常开发中,我们知道,很多UI控件都是在其内部由多个UI空间组合而成,这些内部控件苹果并没有提供访问的API,但是使用KVC可以解决这个问题,常用的就是自定义tabbar
、个性化UITextField
中的placeHolderText
- 5、用
KVC
实现高阶消息传递
在对容器类使用KVC
时,valueForKey
:将会被传递给容器中的每一个对象,而不是对容器本身进行操作,结果会被添加到返回的容器中,这样,可以很方便的操作集合 来返回 另一个集合
如下所示
//KVC实现高阶消息传递
- (void)transmitMsg
NSArray *arrStr = @[@"english", @"franch", @"chinese"];
NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString *str in arrCapStr)
NSLog(@"%@", str);
NSArray *arrCapStrLength = [arrCapStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber *length in arrCapStrLength)
NSLog(@"%ld", (long)length.integerValue);
//********打印结果********
2020-10-27 11:33:43.377672+0800 CJLCustom[60035:6380757] English
2020-10-27 11:33:43.377773+0800 CJLCustom[60035:6380757] Franch
2020-10-27 11:33:43.377860+0800 CJLCustom[60035:6380757] Chinese
2020-10-27 11:33:43.378233+0800 CJLCustom[60035:6380757] 7
2020-10-27 11:33:43.378327+0800 CJLCustom[60035:6380757] 6
2020-10-27 11:33:43.378417+0800 CJLCustom[60035:6380757] 7
以上是关于iOS底层KVC原理的主要内容,如果未能解决你的问题,请参考以下文章