iOS底层之KVC的探索(6)

187 阅读4分钟

前言

上一篇文章,写了runtime的实战应用和Aspects使用,里面有描述到KVC的实战应用。KVC也是我们比较经常用到的知识点,还依稀记得当初入门的时候,model都是写关于KVC的方法,我也不懂为什么,反正就照着写,那今天我们就一起来探索一下。

KVC介绍

定义

KVC全称是(key-Value coding),俗称“键值编码",允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。不需要调用明确的存取方法,这样就可以在运行时动态访问和修改对象的属性,而不是在编译时确定。

常用API

//通过key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//通过key来获取值
- (nullable id)valueForKey:(NSString *)key;
//通过keyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
//通过keyPath来获取值
- (nullable id)valueForKeyPath:(NSString *)key;

keykeyPath 的区别是,key是直接写上属性或者成员变量,keyPath是填上路径。

举个简单例子:

@interface JJPerson : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) JJStudent *stu;

@end

@interface JJStudent : NSObject

@property (nonatomic,strong) NSString *name;

@end

我们直接调用一下

- (void)viewDidLoad {
    [super viewDidLoad];

    JJPerson *person = [JJPerson new];
    [person setValue:@"zhangsan" forKey:@"name"];

    JJStudent *stu = [JJStudent new];
    person.stu = stu;
    [person setValue:@"lisi" forKeyPath:@"stu.name"];

    NSLog(@"person: %@, stu: %@", person.name, person.stu.name);
}

看一下输出结果:

KVCKVODemo[51444:4985374] person: zhangsan, stu: lisi

当然,我们的NSKeyValueCoding 还有其它常用的API。

// 如果返回为YES,则会按照_key,_iskey,key,iskey的顺序搜索成员,默认为YES
+ (BOOL)accessInstanceVariablesDirectly;

// 如果取value的时候,Key不存在,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;

//如果设置value的时候,Key不存在,则会调用这个方法,默认是抛出异常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

// 模型转字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

// 字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

KVC设值原理

我们调用 setValue:forKey:的时候,假设我们调用的是下面的例子,name是成员变量。

JJPerson *person = [JJPerson new];

[person setValue:@"zhangsan" forKey:@"name"];
  1. 首先查找是否有setKey_setKey_setIsKey方法。如果有的话,按顺序只调用一个即可。key是指成员变量。没有则走第二步。
- (void)setName:(NSString *)name{
    NSLog(@"%s - %@", __func__ ,name);
}

- (void)_setName:(NSString *)name{
    NSLog(@"%s - %@", __func__ ,name);
}

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@", __func__ ,name);
}
  1. 没有找到第一步方法,去判断accessInstanceVariablesDirectly是否返回YES,默认是的。如果返回的是YES,就直接按照_key,_iskey,key,iskey的顺序搜索成员变量。设置为NO,就不搜索。如果返回的是NO或者没有找到上面的方法,则进行第3步。

下面4个成员变量,随便写一个都可以,但是如果为NO就会报错。

@interface JJPerson : NSObject
{

    @public

    NSString * _name;

    NSString * _isName;

    NSString * name;

    NSString * isName;

}

+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}

- (void)setValue:(**id**)value forUndefinedKey:(NSString *)key
{
    NSLog(@"%s - %@", __func__ ,key);

}
  1. 上2步都没有成功,这时候系统就会执行该对象的setValue:forUndefinedKey: 方法,如果不重写该方法,则会抛出NSUndefinedKeyException类型异常。

流程图如下:

Snipaste_2022-01-17_23-09-27.png

KVC取值原理

我们调用 valueForKey: 的时候,假设我们调用的是下面的例子,name是成员变量。

JJPerson *person = [JJPerson new];

[person setValue:@"zhangsan" forKey:@"name"];

NSLog(@"取值:%@",[person valueForKey:@"name"]);
  1. 首先查找是否有getKeyKeyisKey_Key方法。如果有的话,按顺序只调用一个即可。key是指成员变量。没有则走第二步。
- (NSString *)getName{
    return NSStringFromSelector( _cmd);
}

- (NSString *)name{
    return NSStringFromSelector( _cmd);
}

- (NSString *)isName{
    return NSStringFromSelector( _cmd);
}

- (NSString *)_name{
    return NSStringFromSelector( _cmd);
}
  1. 没有找到第一步方法,则去判断accessInstanceVariablesDirectly是否返回YES,默认是的。如果返回的是YES,就直接按照_key,_iskey,key,iskey的顺序搜索成员变量,直接访问成员变量的值。设置为NO,就不搜索。如果返回的是NO或者没有找到上面的方法,则进行第3步。

下面4个成员变量,随便写一个都可以,但是如果为NO就会报错。

@interface JJPerson : NSObject
{

    @public

    NSString * _name;

    NSString * _isName;

    NSString * name;

    NSString * isName;

}

+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}

- (**id**)valueForUndefinedKey:(NSString *)key
{
    NSLog(@"%s - %@", __func__ ,key);
    return nil;
}
  1. 上2步都没有成功,这时候系统就会执行该对象的valueForUndefinedKey: 方法,如果不重写该方法,则会抛出NSUndefinedKeyException类型异常。

流程图如下:

Snipaste_2022-01-17_23-29-59.png

KVC作用

KVC的使用可以参考我之前写的一篇文章:runtime的实战应用和Aspects使用

主要作用有:

  • KVC动态的取值和设值。

  • 用KVC来访问和修改私有变量。