前言
上一篇文章,写了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;
key 与 keyPath 的区别是,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"];
- 首先查找是否有
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);
}
- 没有找到第一步方法,去判断
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);
}
- 上2步都没有成功,这时候系统就会执行该对象的
setValue:forUndefinedKey:方法,如果不重写该方法,则会抛出NSUndefinedKeyException类型异常。
流程图如下:
KVC取值原理
我们调用 valueForKey: 的时候,假设我们调用的是下面的例子,name是成员变量。
JJPerson *person = [JJPerson new];
[person setValue:@"zhangsan" forKey:@"name"];
NSLog(@"取值:%@",[person valueForKey:@"name"]);
- 首先查找是否有
getKey,Key,isKey,_Key方法。如果有的话,按顺序只调用一个即可。key是指成员变量。没有则走第二步。
- (NSString *)getName{
return NSStringFromSelector( _cmd);
}
- (NSString *)name{
return NSStringFromSelector( _cmd);
}
- (NSString *)isName{
return NSStringFromSelector( _cmd);
}
- (NSString *)_name{
return NSStringFromSelector( _cmd);
}
- 没有找到第一步方法,则去判断
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;
}
- 上2步都没有成功,这时候系统就会执行该对象的
valueForUndefinedKey:方法,如果不重写该方法,则会抛出NSUndefinedKeyException类型异常。
流程图如下:
KVC作用
KVC的使用可以参考我之前写的一篇文章:runtime的实战应用和Aspects使用。
主要作用有:
-
KVC动态的取值和设值。
-
用KVC来访问和修改私有变量。