OC底层探索(十五)KVC底层原理
KVC
的全称是Key-Value Coding
,翻译成中文是 键值编码
,键值编码是由NSKeyValueCoding
非正式协议启用的一种机制,对象采用该协议来间接访问其属性
。既可以通过一个字符串key
来访问某个属性。这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问。
KVC 相关API
常用方法
- 通过
key
设值/取值
//直接通过Key来取值
- (nullable id)valueForKey:(NSString *)key;
//通过Key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- 通过
keyPath
(即路由
)设值/取值
//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)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 设值 底层原理
在日常开发中,针对对象属性的赋值,一般有以下两种方式
- 直接通过setter方法赋值
- 通过KVC键值编码的相关API赋值
LGPerson *person = [[LGPerson alloc] init];
// 1、一般setter 方法
person.name = @"KC NB";
// 2、KVC方式
[person setValue:@"KC" forKey:@"name"];
下面针对使用最多的KVC
设值方法:setValue:forKey
,来进行其底层原理的探索。
首先进入setValue:forKey
的声明,发现是在Foundation
框架中,而Foundation框架是不开源的,有以下几种方式可以去探索底层
- 通过Hopper反汇编,查看伪代码
- 通过苹果官方文档
- Github搜索是否有相关的demo
在这里,我们通过Key-Value Coding Programming Guide苹果官方文档来研究,针对设值流程,有如下说明:
-
【第一步】首先查找是否有这三种
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
类型的异常
综上所述,KVC通过 setValue:forKey:
方法设值的流程以设置LGPerson的对象person的属性name为例,如下图所示
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
类型的异常
综上所述,KVC通过 valueForKey: 方法取值的流程以设置LGPerson的对象person的属性name为例,如下图所示
上一篇: iOS底层探索(十三) 类的加载(下)
下一篇: java虚拟机基础(二)