简介
KVC(Key-value coding)键值编码。就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。
KVC在iOS的定义
- KVC的定义是对NSObject的扩展来实现的(Objective-C中有个显式的NSKeyValueCoding类别名)。
- 几乎所有的Objective-C对象都能使用KVC
下面介绍一下KVC四个最为重要的方法
//通过key来取值
- (nullable id)valueForKey:(NSString *)key;
//通过key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
当然 NSKeyValueCoding类别中还有其他的一些方法,下面列举一些
//默认返回YES,表示如果没有找到Set方法的话,
会按照_key,_iskey,key,iskey的顺序搜索成员,
设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、
为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue
forKey:(NSString *)inKey error:(out NSError **)outError;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段
或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
KVC底层执行流程
当调用setValue:@"Jack" forKey:@"name"的代码时,底层的执行机制如下:
- 程序优先遍历类对象的方法列表去找调用setName:方法,找到setter方法
- 找到setter方法之后才会遍历类对象的成员列表找到_name赋值。
- 如果没有找到setName:方法,那就继续找_setName方法,
- 如果还没有没有的KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES. 返回NO就是不允许遍历类的成员变量
- 如果你重写了该方法让其返回NO的话,,那么这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。
- KVC机制会搜索该类成员变量列表里面有没有名为的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。
- 类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量。
- 该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
- 上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。
下面我们用代码验证一下:
//Person.h
@interface Person : NSObject {
//不声明@public,Person无法用->来访问成员变量
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
//系统会自动生成setter getter方法
@property (nonatomic, copy) NSString *name;
@end
//person.m文件
@implementation Person
//当找不到set方法,会调用用这个方法
+ (BOOL)accessInstanceVariablesDirectly {
NSLog(@"accessInstanceVariablesDirectly");
return NO;
}
@end
然后在调用该对象并且setValue: forKey:赋值
Person *p = [[Person alloc] init];
[p setValue:@"张三" forKey:@"name"];
NSLog(@"getName: %@", p.name);
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_name: %@", p->_name);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);
执行结果:
getName: 张三
valueforkey: 张三
_name: 张三
_isName1: (null)
name: (null)
isName: (null)
说明setValue: forKey:会对该对象的类对象的方法列表进行遍历,如果找到setName方法成功之后,才会遍历类对象的成员变量找到_name进行赋值。
当注释掉@property (nonatomic, copy) NSString *name;,不让Person自动生成对象setter getter方法,看看怎么执行的
//Person.h
@interface Person : NSObject {
//不声明@public,Person无法用->来访问成员变量
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
//系统会自动生成setter getter方法
//@property (nonatomic, copy) NSString *name;
@end
//person.m文件
@implementation Person
//当找不到set方法,会调用用这个方法
+ (BOOL)accessInstanceVariablesDirectly {
NSLog(@"accessInstanceVariablesDirectly");
return NO;
}
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"获取值出现异常,该key不存在%@",key);
return nil;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"赋值出现异常,该key不存在%@",key);
}
@end
然后在调用该对象并且setValue: forKey:赋值
Person *p = [[Person alloc] init];
[p setValue:@"张三" forKey:@"name"];
//NSLog(@"getName: %@", p.name);//没有getter方法了
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_name: %@", p->_name);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);
执行结果:
// 遍历set、get方法没找到,执行两次
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
赋值出现异常,该key不存在name
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
获取值出现异常,该key不存在name
valueforkey: (null)
_name: (null)
_isName: (null)
name: (null)
isName: (null)
说明了accessInstanceVariablesDirectly返回NO的话,不在遍历类对象成员变量赋值。就会调用setValue: forUndefinedKey。没有setter方法,就没再类对象成员列表去找_name.
那我们把accessInstanceVariablesDirectly返回的事YES,执行结果:
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
_name: 张三
_isName: (null)
name: (null)
isName: (null)
说明先找遍历_name变量,找到之后就退出遍历。不在遍历其他变量。
那就把对象的_name去掉
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);
执行结果:
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
_isName: 张三
name: (null)
isName: (null)
说明在类的成员列表里遍历没见到_name变量,在遍历会找_isName,找到之后就退出遍历。不在遍历其他变量。那我们把_isName也去掉。打印一下:
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);
执行结果:
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
name: 张三
isName: (null)
说明在类的成员列表里遍历没见到_name、_isName变量,在遍历会找name变量,找到之后就退出遍历。不在遍历其他变量。那我们把name也去掉。打印一下:
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"isName: %@", p->isName);
执行结果打印一下:
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
isName: 张三
说明在类的成员列表里遍历没见到_name、_isName、name变量,在遍历找到isName变量,找到之后就退出遍历,不在遍历其他变量。把变量都注释掉。打印一下:
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
// 遍历set、get方法没找到,执行两次
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
赋值出现异常,该key不存在name
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
获取值出现异常,该key不存在name
valueforkey: (null)
当执行setvalue:forkey没有set方法、并且依次遍历_name、_isName、name、isName没有的时候就会执行setValue:(id)value forUndefinedKey并默认是报错的。重写方法做处理不会报错。
KVC的使用
-
动态地取值和设值
- 利用KVC动态的取值和设值是最基本的用途了。相信每一个iOS开发者都能熟练掌握,
-
用KVC来访问和修改私有变量
- 对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的。
-
Model和字典转换
- 充分地运用了KVC和Objc的runtime组合的技巧,只用了短短数行代码就是完成了很多功能
-
KVO是基于KVC实现的
总结
通过分析KVC整个执行过程,了解NSKeyValueCoding这个分类的方法,是自己对KVC使用加深印象。
网友评论