一、KVC简介
KVC:键值编码,使用字符串直接访问对象的属性
常用方法
@interface NSObject(NSKeyValueCoding)
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
@end
NSKeyValueCoding
其他方法
+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值正确性�验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
二、实现原理
setValue:forKey:
原理
- 查找是否实现
setter
方法,如果有,优先调用setter
方法完成赋值(注意:set
后面的键的第一字字母必须是大写!!) - 当没找到
setter
方法,调用accessInstanceVariablesDirectly
询问;如果返回YES
,顺序匹配变量名与_<key>
,_is<Key>
,<key>
,is<Key>
,匹配到则设定其值,如果返回NO
,结束查找,并调用setValue:forUndefinedKey:
报异常 - 如果既没有
setter
也没有实例变量时,调用setValue:forUndefinedKey:
valueForKey:
的原理
-
kvc
取值按照getKey、key、iskey、_key
顺序查找方法 - 存在直接调用
- 没找到同样,先查看
accessInstanceVariablesDirectly
方法 - 如果可以访问会按照
_key、_isKey、key、iskey
的顺序查找成员变量 - 找到直接取值
- 如果所有情况都失败,则调用
valueForUndefinedKey:
方法并抛出NSUnkonwKeyException
异常,这是默认行为,但是子类可以重写此方法。
三、搜索规则
NSMutableArray 、NSMutableSet
和NSMutableOrderedSet
对应的搜索规则
以NSMutableSet
为例说明:
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
搜索
addObject<Key>Object:
,remove<Key>Object:
或者add<Key> , remove<Key>
格式的方法
如果至少找到一个insert
方法和一个remove
方法,那么同样返回一个可以响应NSMutableSet
所有方法代理集合(类名是NSKeyValueFastMutableSet2
),那么给这个代理集合发送NSMutableSet
的方法,以addObject<Key>Object: , remove<Key>Object:
或者add<Key> , remove<Key>
组合的形式调用。还有两个可选实现的接口:intersect<Key>
,set<Key>:
。如果
receiver
是ManagedObject
,那么就不会继续搜索如果上一步的方法没有找到,则搜索
set<Key>:
格式的方法,如果找到,那么发送给代理集合的NSMutableSet
最终都会调用set<Key>:
方法。如果上一步的方法还没有找到,再检查类方法
+ (BOOL) accessInstanceVariablesDirectly
,如果返回YES
(默认行为),会按_<key>,<key>
,的顺序搜索成员变量名,如果找到,那么发送的NSMutableSet
消息方法直接交给这个成员变量处理。如果还是找不到,则调用
valueForUndefinedKey:
。
四、适用场景
- 动态取值与设值
setValue:forKeyPath:
和valueForKeyPath:
可以嵌套层级setObject:forKey:
和ObjectForKey:
中的key
可以不为NSString
setObject:forKey:
中的object
不能为空,但是另外两者可以
- 字典转模型
- 访问或修改私有变量,iOS13之后苹果限制了此方法
例如在
.m
中定义的私有成员变量和属性,都可以通过KVC
的方式访问。
这个操作对readonly
的属性,@protected
的成员变量,都可以正常访问。如果不想让外界访问类的成员变量,则可以将accessInstanceVariablesDirectly
属性赋值为NO
。
- 修改一些控件的内部属性
众所周知很多
UI
控件都由很多内部UI
控件组合而成的,但是Apple
度没有提供这访问这些控件的API
,这样我们就无法正常地访问和修改这些控件的样式。而KVC
在大多数情况可下可以解决这个问题。
五、KVC异常处理
-
key
或者keyPath
发生错误
当根据
KVC
搜索规则,没有搜索到对应的key
或者keyPath
,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException
的异常,并且应用程序Crash
我们可以重写下面两个方法(这种方法只能运用在自己类的对象中):
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- 传参为
nil
通常情况下,
KVC
不允许你要在调用setValue:
属性值forKey:
(或者keyPath
)时对非对象传递一个nil
的值;如果你不小心传了,KVC
会调用setNilValueForKey:
方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。
我们可以重写这个方法:
-(void)setNilValueForKey:(NSString *)key{
NSLog(@"不能将%@设成nil",key);
}
- 使用
swizzling