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:除了能接受当前类的属性,还能接受当前类属性的属性,即可以接受关系链
用法:
        
        Person *personPath = [[Person alloc] init];
        [personPath setValue:@"I am Father" forKey:@"name"];
        NSLog(@"%@", [personPath valueForKey:@"name"]);
输出结果:
 
        
        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。
@interface StudentModel : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
@property (nonatomic, strong) NSString *studentSex;
@end
@implementation StudentModel
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"sex"]) {
        self.studentSex = (NSString *)value;
    }
}
@end
        
        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的其他方法
+ (BOOL)accessInstanceVariablesDirectly;
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- (void)setNilValueForKey:(NSString *)key;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
- (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。