KVC
是我们在开发过程中经常会用到的东西,但是很多时候,只是使用,不求甚解。那么我们今天来探索一下 -- KVC
什么是KVC?
KVC
全称是Key-Value Coding
在苹果的开发文档Key-Value Coding Programming Guide中,我们可以看到这样的描述
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.
它定义由NSKeyValueCoding.h
非正式协议启用的一种机制。对象采用这种机制来提供对其属性的间接访问。当对象符合键值编码时,可通过简洁,统一的消息传递接口通过字符串参数来访问其属性。这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。
同时,KVC
也是许多其他Cocoa技术
的基础概念,如KVO
、Core Data
KVC相关API
而其中最常用到的,也是我们今天主要研究的 就是
///通过key 取值
- (nullable id)valueForKey:(NSString *)key;
///通过key 赋值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
KVC赋值的底层原理
KVC
所依赖的NSKeyValueCoding.h
是位于Foundation
框架中的,而这个Foundation
框架目前苹果并没有开源。所以我们主要依赖其官方文档Key-Value Coding Programming Guide去进行探索。
针对赋值,文档中有这样一段
Search Pattern for the Basic Setter
The default implementation of setValue:forKey:, given key and value parameters as input, attempts to set a property named key to value (or, for non-object properties, the unwrapped version of value, as described in Representing Non-Object Values) inside the object receiving the call, using the following procedure:
Look for the first accessor named set<Key>: or _set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.
If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.
Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.
总结起来就是
对于setValue:forKey:
给定的key
和value
参数作为输入,尝试设置命名属性key到value主要流程为
- 按顺序查找名为
set<Key>
或_set<Key>
方法,如果找到,直接调用。如果没找到,则第2步 - 调用类方法
accessInstanceVariablesDirectly
如果返回Yes
,则进入间接访问实例化赋值,寻找名为_<key>
,_is<Key>
,<key>
,或者is<Key>
的变量,如果找到,则直接使用输入值设置变量。 - 如果返回
NO
或者没找到上面的值,则调用setValue:forUndefinedKey:
,默认会抛出异常。
我们通过举例来说明
FQPerson * person = [[FQPerson alloc] init];
[person setValue:@"fangqiang" forKey:@"name"];
其实际流程如下
KVC取值的底层原理
Accessor Search Patterns
The default implementation of the NSKeyValueCoding protocol provided by NSObject maps key-based accessor calls to an object’s underlying properties using a clearly defined set of rules. These protocol methods use a key parameter to search their own object instance for accessors, instance variables, and related methods that follow certain naming conventions. Although you rarely modify this default search, it can be helpful to understand how it works, both for tracing the behavior of key-value coded objects, and for making your own objects compliant.
NOTE
The descriptions in this section use <key> or <Key> as a placeholder for the key string that appears as a parameter in one of the key-value coding protocol methods, which is then used by that method as part of a secondary method call or variable name lookup. The mapped property name obeys the placeholder’s case. For example, for the getters <key> and is<Key>, the property named hidden maps to hidden and isHidden.
Search Pattern for the Basic Getter
The default implementation of valueForKey:, given a key parameter as input, carries out the following procedure, operating from within the class instance receiving the valueForKey: call.
Search the instance for the first accessor method found with a name like get<Key>, <key>, is<Key>, or _<key>, in that order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step.
If no simple accessor method is found, search the instance for methods whose names match the patterns countOf<Key> and objectIn<Key>AtIndex: (corresponding to the primitive methods defined by the NSArray class) and <key>AtIndexes: (corresponding to the NSArray method objectsAtIndexes:).
If the first of these and at least one of the other two is found, create a collection proxy object that responds to all NSArray methods and return that. Otherwise, proceed to step 3.
The proxy object subsequently converts any NSArray messages it receives to some combination of countOf<Key>, objectIn<Key>AtIndex:, and <key>AtIndexes: messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name like get<Key>:range:, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSArray, even if it is not.
If no simple accessor method or group of array access methods is found, look for a triple of methods named countOf<Key>, enumeratorOf<Key>, and memberOf<Key>: (corresponding to the primitive methods defined by the NSSet class).
If all three methods are found, create a collection proxy object that responds to all NSSet methods and return that. Otherwise, proceed to step 4.
This proxy object subsequently converts any NSSet message it receives into some combination of countOf<Key>, enumeratorOf<Key>, and memberOf<Key>: messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSSet, even if it is not.
If no simple accessor method or group of collection access methods is found, and if the receiver's class method accessInstanceVariablesDirectly returns YES, search for an instance variable named _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6.
If the retrieved property value is an object pointer, simply return the result.
If the value is a scalar type supported by NSNumber, store it in an NSNumber instance and return that.
If the result is a scalar type not supported by NSNumber, convert to an NSValue object and return that.
If all else fails, invoke valueForUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.
对于取值,文档中有以上说明,其实类似于上面的赋值原理。
在valueForKey:
给定key
参数作为输入的情况下
- 首先查找
getter
方法,按照get<Key> -> <key> -> is<Key> -> _<key>
的方法顺序查找 如果找到则进入步骤5,如果没找到,则下一步 - 如果步骤1中的
getter
方法没有找到,KVC
会查找countOf <Key>
和objectIn <Key> AtIndex :
和<key> AtIndexes :
如果找到其中的第一个以及其他两个中的至少一个,则创建一个响应所有NSArray
方法的集合代理对象,并返回该对象。否则,请继续执行步骤3。代理对象随后将任何NSArray
接收到的一些组合的消息countOf<Key>
,objectIn<Key>AtIndex:
和<key>AtIndexes:
消息给KVC
创建它兼容的对象。如果原始对象还实现了名称为的可选方法get<Key>:range:
,则代理对象也会在适当时使用该方法。 - 如果没有找到简单的访问方法或阵列访问方法组,寻找一个三重的方法命名
countOf<Key>,enumeratorOf<Key>
和memberOf<Key>:
(对应于由所定义的原始的方法NSSet
类)。如果找到所有三个方法,请创建一个响应所有NSSet
方法的集合代理对象并将其返回。否则,请继续执行步骤4。 - 如果此时还未找到,且接收器的类方法
accessInstanceVariablesDirectly
返回YES
,搜索名为实例变量_<key>
,_is<Key>
,<key>
,或者is<Key>
,按照这个顺序。如果找到,请直接获取实例变量的值,然后继续执行步骤5。否则,请继续执行步骤6。 - 如果检索到的属性值是对象指针,则只需返回结果。
如果该值是所支持的标量类型NSNumber,则将其存储在NSNumber实例中并返回该实例。
如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象。 - 如果其他所有方法均失败,请调用
valueForUndefinedKey:
。默认情况下会引发异常
自定义KVC
其实在这里,我们就可以大概根据流程去实现一个自定义KVC