KVC本质

文章目录

  • 什么是KVC
  • API
  • setValueForKey
    • setNilValueForKey、setValue:(id)value forUndefinedKey:(NSString *)key
  • ValueForKey
  • 传递nil
  • 处理非对象。
  • KVC与容器类
    • 不可变有序容器
    • 可变有序容器
      • 触发KVO
    • 无序可变容器

什么是KVC

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性

API

-(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
-(void)setValue:(id)value forKey:(NSString *)key;
-(id)valueForKeyPath:(NSString *)keyPath;
-(id)valueForKey:(NSString *)key;

key与keypath的区别:
KVC本质_第1张图片
keypath可以写多级访问。
其他api

+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set方法的话,会按照_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 *)dictionaryWithValuesForKeys:(NSArray *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典

setValueForKey

KVC本质_第2张图片
1.按照setKey:,_setKey:顺序查找方法,找到了就调用方法传递参数。
2.第一步没找到就会调用accessInstanceVariablesDirectly方法,该方法返回值为NO时直接调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException,方法返回值是YES的时候进入第三步。该方法默认值是返回YES。
3.按照_key、_isKey、key、isKey顺序查找成员变量,找到了就直接赋值,没找到依然是调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException。

_setKey方法是啥东西?
我测试了一下,当属性为@dynamic时,手动实现 _setKey方法的话就会走这_setKey.也就是说KVC会寻找合适的存取方法来达到赋值目的。
然后调用accessInstanceVariablesDirectly,如果return NO或者找不到这个方法实现就抛出异常。
在return YES的情况下, 会按照顺序查找_key、_isKey、key、isKey等成员变量, 如果找不到依然会抛出NSUnknownKeyException异常
下面来验证查找顺序。

 [person setValue:@20 forKey:@"age"];

KVC本质_第3张图片

KVC本质_第4张图片
我如果把_age注释了
KVC本质_第5张图片
KVC本质_第6张图片

setNilValueForKey、setValue:(id)value forUndefinedKey:(NSString *)key

俩者都是用来调试的方法。
如果重写了setNilValueForKey方法,给key是int、long等基本类型赋值nil时,会触发该方法。
不重写的话是该方法抛出异常。
setValue:(id)value forUndefinedKey:(NSString *)key
是在没有找到setKey、_setKey方法,成员变量也没有找到的时候会调用,可以打印出此时的key和value,常用在字典给model赋值调试用。

ValueForKey

KVC本质_第7张图片
和set的流程一致。

传递nil

当我们setValue的时候,如果传递的是nil值,会调用

-(void)setNilValueForKey:(NSString *)key{
NSLog(@“不能将%@设成nil”,key);
}
这个方法默认是抛出异常,我们可以重写它。

处理非对象。

setValue时,如果要赋值的对象是基本类型,需要将值封装成NSNumber或者NSValue。
如果valueforkey时,返回的是id类型的对象,基本类型数据也会被封装成NSNumber或者NSValue。

KVC与容器类

不可变有序容器

对于不可变有序容器,可以使用valueforKey来获取
KVC本质_第8张图片

可变有序容器

KVC本质_第9张图片
用valueforKey也能返回。
下面我们看一下方法- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;的深层次原理。

搜索insertObject:inAtIndex: , removeObjectFromAtIndex:或者 insertAdIndexes ,removeAtIndexes格式的方法。如果至少找到一个insert方法和一个remove方法,那么同样返回一个可以响应NSMutableArray所有方法代理集合(类名是NSKeyValueFastMutableArray),那么给这个代理集合发送NSMutableArray的方法,以insertObject:inAtIndex:, removeObjectFromAtIndex: 或者 insertAdIndexes , removeAtIndexes组合的形式调用。还有两个可选实现的接口:replaceOnjectAtIndex:withObject:,replaceAtIndexes:with:。

如果上步的方法没有找到,则搜索set: 格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set:方法。 也就是说,mutableArrayValueForKey:取出的代理集合修改后,用set:重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。

如果上一步的方法还还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),会按_、,的顺序搜索成员变量名,如果找到,那么发送的NSMutableArray消息方法直接交给这个成员变量处理。

如果还是找不到,则调用valueForUndefinedKey:。
在这里插入图片描述
在这里插入图片描述

触发KVO

对NSMutableArray、NSMutableDictiory等进行KVO监听时,如果是调用add、remove等方法那么不会触发set、get方法,KVO无法监听。
从上面看,mutableArrayValueForKey方法最后会对集合对象进行set方法调用也就说,对代理集合中的成员进行操作后(remove、add),代理集合会执行setkey方法对原集合修改,这样就触发了集合对象的set方法。
[[self mutableArrayValueForKey:@“kvcArrM”] addObject:@“Two”];这样的方式就能触发KVO。

无序可变容器

  • (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
    对NSMutableSet进行操作。
    和上面的类似,搜索到至少一种添加和删除的方法,产生一个代理集合,最终还是会走集合的set方法,将修改的代理集合赋值回去。

你可能感兴趣的:(KVC本质)