iOS底层原理 - 探寻KVC本质

·  阅读 703

KVC的全称是Key-Value Coding,翻译成中文是 键值编码,键值编码是由NSKeyValueCoding非正式协议启用的一种机制,对象采用该协议来间接访问其属性,即可以通过一个字符串key来访问某个属性。这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问。

1.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;

//在设置值之前检验键名字是否有效 它可以用来检查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给一个基本数据类型比如`int` `float`设置一个`nil`值,就触发调用这个方法 可以在这个方法里做异常处理或者重新给key赋值
- (void)setNilValueForKey:(NSString *)key;

//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

//用于将字典转到Model,通过一个字典来修改Model对应key的值。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
复制代码

2.KVC API 使用

定义一个模型类

#import <Foundation/Foundation.h>
@interface ZBMVVMSimpleModel : NSObject
@property (nonatomic,copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
复制代码
  • 通过key 设值/取值
- (void)viewDidLoad {
    [super viewDidLoad];
    ZBMVVMSimpleModel *simpleModel = [[ZBMVVMSimpleModel alloc]init];
    ///存储值
    [simpleModel setValue:@"ios" forKey:@"name"];
    ///取值
    NSString *name = [simpleModel valueForKey:@"name"];
    NSLog(@"取值 --  %@",name);   
 }
 
控制台打印
ZCDataAnalytics[2115:497383] 取值 --  ios

复制代码
  • 通过keyPath (即路由)设值/取值
@interface BOTSimpleModel : NSObject
@property (nonatomic,copy) NSString *subject;
@end

@interface ZBMVVMSimpleModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) BOTSimpleModel *model;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    ZBMVVMSimpleModel *simpleModel = [[ZBMVVMSimpleModel alloc]init];

    
    BOTSimpleModel *model  = [[BOTSimpleModel alloc]init];
    simpleModel.model = model;
    // 2、KVC方式
    [simpleModel setValue:@"Swift" forKeyPath:@"model.subject"];
    NSLog(@"subject --- %@",[simpleModel valueForKeyPath:@"model.subject"]);
 }
 
控制台打印
ZCDataAnalytics[2115:497383] subject --- Swift

复制代码

【小结】

  • keyPath 相当于根据路径去寻找属性,一层一层往下找。
  • key 是直接根据属性名字设置值,如果按路径找会报错。

3.KVC 设置的原理

setValue:forKey: 赋值的原理

image.png 步骤:当我们设置setValue:forKey:时;
① 首先会查找 setKey:_setKey: (按顺序查找);

查找 setName:

image.png image.png

查找_setName:

image.png image.png

② 如果有直接调用,如果没有,先查看 accessInstanceVariablesDirectly 方法;

+ (BOOL)accessInstanceVariablesDirectly{
     return YES;   /// 可以直接访问成员变量
     //return NO;  ///  不可以直接访问成员变量,  
     /// 直接访问会报NSUnkonwKeyException错误  
 }
复制代码

③ 如果可以访问会按照 _key_isKeykeyiskey的顺序查找成员变量,找到直接赋值;

查找_key

image.png

image.png

查找_isKey

image.png

查找key

image.png

查找iskey

image.png

④ 未找到报错NSUnkonwKeyException错误

image.png

【 小结 】
所以,setValue:@10 forKey:@"age" 修改age,核心还是应为调用了willChangeValueForKey:keydidChangeValueForKey:key

4.KVC 取值的原理

image.png

步骤:当我们设置valueForKey:时;
① kvc取值按照 getKey、key、iskey、_key 顺序查找;

② 存在直接调用,如果没找到,同样会先查看accessInstanceVariablesDirectly方法;

+ (BOOL)accessInstanceVariablesDirectly{
     return YES;   /// 可以直接访问成员变量
     //return NO;  ///  不可以直接访问成员变量,  
     /// 直接访问会报NSUnkonwKeyException错误  
 }
复制代码

③ 如果可以访问会按照 _key、_isKey、key、iskey的顺序查找成员变量,找到直接赋值;

image.png image.png

④ 未找到报错NSUnkonwKeyException错误。 image.png

5.总结

1.通过KVC修改属性会触发KVO么?

会触发。
这问题也就是问,下面两段代码的差异。

ZBMVVMSimpleModel *simpleModel = [[ZBMVVMSimpleModel alloc]init];
simpleModel.name  = @"ios";
// --------------- VS ----------------
ZBMVVMSimpleModel *simpleModel = [[ZBMVVMSimpleModel alloc]init];
[simpleModel setValue:@"ios" forKey:@"name"];

解析:
- (void)viewDidLoad {
    [super viewDidLoad];
    ZBMVVMSimpleModel *simpleModel = [[ZBMVVMSimpleModel alloc]init];
    
    //数据初始化
    [simpleModel setValue:@"ios" forKey:@"name"];
    //添加监听
    [simpleModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:**nil**];
    //重新赋值 
    //simpleModel.name  = @"ios";
    [simpleModel setValue:@"swift" forKey:@"name"];
    [simpleModel removeObserver:self forKeyPath:@"name"];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(**id**)object change:(NSDictionary<NSKeyValueChangeKey,**id**> *)change context:(**void** *)context{
    NSLog(@"change -- %@",change);
}

日志打印
**2022-04-01 15:48:37.910300+0800 ZCDataAnalytics[2383:551351] change -- {**
**kind = 1;**
**new = swift;**
**old = ios;**
**}**
复制代码

通过打印得到,通过KVC修改属性会触发KVO。

分析:
它们的本质都一样,都能出发KVO,都会调用[self willChangeValueForKey:key];[self didChangeValueForKey:key];
等同于手动出发了KVO

//    [simpleModel willChangeValueForKey:@"name"];
//    simpleModel -> _name = @"swift";
//    [simpleModel didChangeValueForKey:@"name"];
复制代码
2.KVC的赋值和取值过程是怎样的?原理是什么?

setValue:forKey: 赋值的原理
① 首先会查找setKey:、_setKey: (按顺序查找);
② 如果有直接调用,如果没有,先查看accessInstanceVariablesDirectly方法;
③ 如果可以,访问会按照 _key、_isKey、key、iskey的顺序查找成员变量,找到直接赋值;
④ 未找到报错NSUnkonwKeyException错误。

valueForKey: 取值的原理
① kvc取值按照 getKey、key、iskey、_key 顺序查找;
② 存在直接调用,如果没找到,同样会先查看accessInstanceVariablesDirectly方法;
③ 如果可以访问会按照 _key、_isKey、key、iskey的顺序查找成员变量,找到直接赋值
④ 未找到报错NSUnkonwKeyException错误。

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改