KVC
文章目录
- KVC
 - KVC常用的四种方法
 
- key和keyPath的区别
 - 用法:
 
- 批量存值操作
 - 批量赋值操作
 - 字典模型相互转化
 - KVC的其他方法
 
- KVC原理探索
 - `setValue:forKey:` 的原理(KVC赋值原理)
 - `valueForKey:`的原理(KVC取值原理)
 
- 注意事项
 
KVC的全称是KeyValueCoding,俗称“键值编码”,可以通过一个key来访问某个属性;
KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量;
它是一个非正式的Protocol,提供一种机制来间接访问对象的属性,而不是通过调用Setter、Getter方法访问。KVO 就是基于 KVC 实现的关键技术之一。
KVC常用的四种方法
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
 
key和keyPath的区别
key:只能接受当前类所具有的属性,不管是自己的,还是从父类继承过来的
 keypath:除了能接受当前类的属性,还能接受当前类属性的属性,即可以接受关系链
用法:
        //key的设值取值
        Person *personPath = [[Person alloc] init];
        [personPath setValue:@"I am Father" forKey:@"name"];
        NSLog(@"%@", [personPath valueForKey:@"name"]);
 
输出结果:
 
        //keypath的设值取值
        personPath.son = [[PersonSon alloc] init];
        [personPath setValue:@"I am Son" forKeyPath:@"son.sonName"];
        NSLog(@"%@", [personPath valueForKeyPath:@"son.sonName"]);
        NSLog(@"%@", personPath.son.sonName);
 
输出结果:
 
批量存值操作
KVC还有更强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回;
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
 
批量赋值操作
同样,也可以通过KVC进行批量操作,使用对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包好key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给对象的属性赋值。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
 
示例代码:
        //逐个赋值
        Person *personFirst = [[Person alloc] init];
        [personFirst setValue:@"zxb" forKey:@"name"];
        [personFirst setValue:@20 forKey:@"age"];
        [personFirst setValue:@"男" forKey:@"sex"];
        NSLog(@"name = %@, age = %ld, sex = %@",personFirst.name, (long)personFirst.age, personFirst.sex);
        
        //通过字典赋值取值
        NSDictionary *dictionaryFirst = [personFirst dictionaryWithValuesForKeys:@[@"name", @"age", @"sex"]];
        NSLog(@"dictionaryFirst = %@", dictionaryFirst);
        
        NSDictionary *dictionarySecond = @{@"name":@"zzy", @"age":@11, @"sex":@"女"};
        Person *personSecond = [[Person alloc] init];
        [personSecond setValuesForKeysWithDictionary:dictionarySecond];
        NSLog(@"name = %@, age = %ld, sex = %@",personSecond.name, (long)personSecond.age, personSecond.sex);
 
输出结果:
 
字典模型相互转化
如果model属性和dic不匹配,可以重写方法-(void)setValue:(id)value forUndefinedKey:(NSString *)key。
//StudentModel.h
@interface StudentModel : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
@property (nonatomic, strong) NSString *studentSex;
@end
//StudentModel.m
@implementation StudentModel
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"sex"]) {
        self.studentSex = (NSString *)value;
    }
}
@end
 
//main.m
        //字典模型相互转化
        NSDictionary *dictionary = @{@"name":@"stu1", @"age":@66, @"sex":@"nv"};
        StudentModel *model = [[StudentModel alloc] init];
        [model setValuesForKeysWithDictionary:dictionary];
        NSLog(@"model.name:%@",model.name);
        NSLog(@"model.age:%@",model.age);
        NSLog(@"model.sex:%@",model.studentSex);
        
        NSDictionary *tempModelDictionary = [model dictionaryWithValuesForKeys:@[@"name", @"age", @"studentSex"]];
        NSLog(@"tempModelDictionary : %@", tempModelDictionary);
 
输出结果:
 
KVC的其他方法
// 默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;
// KVC提供属性值正确性验证的API,它可以用来检查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方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
// 使用字典为Model赋值
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
// 输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
 
KVC原理探索
setValue:forKey: 的原理(KVC赋值原理)
 
- 首先会按照
setKey、_setKey的顺序查找方法,找到方法,直接调用方法并赋值; - 未找到方法,则调用
+ (BOOL)accessInstanceVariablesDirectly(是否可以直接访问成员变量,默认返回YES); - 若
accessInstanceVariablesDirectly方法返回YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到直接赋值,找不到则抛出NSUnknowKeyExpection异常; - 若
accessInstanceVariablesDirectly方法返回NO,那么就会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常; 

这里提到了setValue:forUndefinedKey方法,这个方法怎么用呢?
在 iOS 的 KVC(键-值编码)中,如果调用了一个对象的 setValue:forKey: 方法,但是这个对象并没有对应的属性或者实例变量来存储这个值,那么就会触发这个对象的 setValue:forUndefinedKey: 方法。这个方法的作用就是在运行时动态地为这个对象添加新的属性或者实例变量,并将这个值存储到新添加的属性或实例变量中。
 需要注意的是,如果一个类没有实现 setValue:forUndefinedKey: 方法,那么默认会抛出一个异常。因此,如果你在使用 KVC 的时候发现程序崩溃了,并且错误信息中包含 “NSUnknownKeyException” 这个字符串,那么很可能就是因为这个原因。
valueForKey:的原理(KVC取值原理)
 
- 首先会按照
getKey、key、isKey、_key的顺序查找方法,找到直接调用取值 - 若未找到,则查看
+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出NSUnknowKeyExpection异常; - 若返回的
YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到则取值; - 找不到则调用
valueForUndefinedKey:抛出NSUnknowKeyExpection异常;

 
注意事项
key的值必须正确,如果拼写错误,会出现异常。- 当
key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来。 - 因为类可以反复嵌套,所以有个
keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去。 NSArray/NSSet等都支持KVC。- 可以通过
KVC访问自定义类型的私有成员。 - 如果对非对象传递一个
nil值,KVC会调用setNIlValueForKey方法,我们可以重写这个方法来避免传递nil出现的错误,对象并不会调用这个方法,而是会直接报错。 - 处理非对象,
setValue时,如果要赋值的对象是基本类型,需要将值封装成NSNumber或者NSValue类型,valueForKey时,返回的是id类型的对象,基本数据类型也会被封装成NSNumber或者NSValue。valueForKey可以自动将值封装成对象,但是setValue:forKey:却不行。我们必须手动讲值类型转换成NSNumber/NSValue类型才能进行传递initWithBool:(BOOL)value。 



















