KVC(Key-Value Coding)键值编码
苹果官方文档地址:https://developer.apple.com/library/archive/navigation/
KVC是利用NSKeyValueCoding 非正式协议实现的一种机制,在开发中可以通过 key 直接访问对象的属性,或者对属性进行赋值操作,而不需要调用明确的存取方法。它允许我们在
运行时
去动态
地访问和修改对象的属性,而不是在编译时决定。
一、KVC原理分析
1、设值流程分析
### 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](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/DataTypes.html#//apple_ref/doc/uid/20002171-BAJEAIEE)) inside the object receiving the call, using the following procedure:
1. 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.
2. 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.
3. 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:
之后,底层的操作如下:
1、底层会优先调用
set<Key>
、_set<Key>
方法,按照这个先后顺序,且只实现其中一个方法;
2、如果没有上述方法,就会调用accessInstanceVariablesDirectly
进行判断,如果返回NO,直接进入下面3步骤,否则YES,调用实例变量赋值顺序_<key>
,_is<Key>
,<key>
,is<Key>
,且只实现其中一个方法;
3、如果没有找到存取方法或者实例变量就会调用setValue:forUndefinedKey:
方法。
2、取值流程分析
### 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.
1. 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.
2. 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](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSArrayClassCluster/Description.html#//apple_ref/occ/cl/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.
3. 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](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSSetClassCluster/Description.html#//apple_ref/occ/cl/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.
4. If no simple accessor method or group of collection access methods is found, and if the receiver's class method `[accessInstanceVariablesDirectly](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/EOF/EOControl/Classes/NSObjectAdditions/Description.html#//apple_ref/occ/clm/NSObject/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.
5. 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.
6. If all else fails, invoke `[valueForUndefinedKey:](https://developer.apple.com/documentation/objectivec/nsobject/1413457-value)`. This raises an exception by default, but a subclass of `NSObject` may provide key-specific behavior.
输入valueForKey:
之后,底层的操作如下:
1、依次查找方法
get<key>
,<key>
,is<key>
,_<key>
,如果能找到,进行5步骤,否则进行下一步;
2、如果没有上面的方法,就去查找countOf<Key>
,objectIn<Key>AtIndex:
,<key>AtIndexes:
的方法,看是否是属于NSArray
;
3、如果没有找到属于NSArray
,就看countOf<Key>
,enumeratorOf<Key>
,memberOf<Key>:
是否是属于NSSet
;
4、如果上面方法都不存在,就查找类方法accessInstanceVariablesDirectly
,如果返回为NO
,就直接进入6步骤,否则YES
, 依次显示实例变量的值_<key>
,_is<Key>
,<key>
,is<Key>
,且只显示一个,然后进行下一步;
5、如果获取到的属性值是一个对象指针,直接返回结果;如果该值是支持NSNumber
类型,返回NSNumber
类型实例;如果该值不支持NSNumber
类型,返回NSValue
对象;
6、如果以上步骤都不是,就调用valueForUndefinedKey:
方法报错提醒。
二、赋值为空判断个例
@interface TestKVC : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *subject;
@end
@implementation TestKVC
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"key:%@是空值",key);
}
@end
//此处省略
........
TestKVC *test = [TestKVC alloc];
[test setValue:nil forKey:@"age"];
[test setValue:nil forKey:@"subject"];
打印结果:key:age是空值,subject对象并不会打印
/* Given that an invocation of -setValue:forKey: would be unable to set the keyed value because the type of the parameter of the corresponding accessor method is an NSNumber scalar type or NSValue structure type but the value is nil, set the keyed value using some other mechanism. The default implementation of this method raises an NSInvalidArgumentException. You can override it to map nil values to something meaningful in the context of your application.
*/
- (void)setNilValueForKey:(NSString *)key;
查找方法注释了解到,空值的打印只支持
NSNumber
和structure
.