一、什么是KVC
KVC(Key Value Coding)直译为键值编码,通俗的来讲就是苹果提供了一套通过字符串
以runtime
的方式访问属性的方法。KVC是用过Category的方式实现的(见Foundation框架NSKeyValueCoding.h),这就意味着几乎所有继承NSObject的对象,都可以使用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;
//是否允许使用KVC直接访问实例变量, 默认YES
- (BOOL)accessInstanceVariablesDirectly;
//校验值是否正确;不正确的值将被替换值或者拒绝设置新值并返回错误原因。
- (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;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
三、使用方法
首先假设现有‘商品’类 Product,以及商品对象prod;代码如下:
@interface Product : NSObject
@property (nonatomic, copy) NSString *name; //商品名字
@property (nonatomic, assign) float price; //商品价格
@property (nonatomic, strong) Factory *factory; //生产工厂
@end
int main(int argc, char * argv[]) {
Product *prod = [[Product alloc] init];
prod.name = @"商品名字";
prod.price = 100;
}
1、取值的用法:
格式:id value = [obj valueForKey:key];
举例:NSString *name = [prod valueForKey:@"name"];
取值时,key 的取值就是用属性名声明的字符串。当我们发送valueForKey:
消息时,系统会根据key值按照如下的步骤进行查找:
第一步:查找 -get<Key>, -<key>, or -is<Key>方法(标量会被转化成NSNumber,NSValue等);如果没找到,则进行第二步
第二步::查找NSOrderSet的 -countOf , -indexInOfObject: and -objectInAtIndex: ,-AtIndexes: ,如果有,则产生一个NSOderSet的代理类,通过找到的方法组合响应valueForKey方法;如果没有则进行第三步
第三步:查找NSArray的-countOf, -objectInAtIndex: ,-AtIndexes: 如果有,则产上NSArray的代理类,通过找的方法组合响应valueForKey方法 ;否则进行第四步
第四步:查找NSSet的-countOf, -enumeratorOf, and -memberOf: 如果有,则创建一个NSSet的代理类,通过找的方法组合响应valueForKey方法;否则进行第五步
第五步:如果+accessInstanceVariablesDirectly返回YES,则按顺序查找_<key>, _is<Key>, <key>, or is<Key>,如果还未找到,则进行第六步
第六步:触发valueForUndefinedKey方法(默认会抛出异常)
2、赋值的用法:
格式:[obj setValue:value forKey:key];
举例:[prod setValue:@"新商品" forKey:@"name"];
当我们发送setValue:forKey:
消息时,系统会按照如下方式进行查找赋值:
第一步:查找-set:方法,如果找到则执行该方法(value必须为对象类型,否则会触发setNilValueForKey方法);如果没有找到,则进行第二步
第二步:如果+accessInstanceVariablesDirectly方法返回的是YES,则按顺序查找 _<key>, _is<Key>, <key>, or is<Key>方法,如果找到了就执行赋值操作(对象类型:先释放旧对象,再进行赋值操作;基本类型:把对象类型转化为基本类型,如NSNumber类型转化为int,long 等)
第三步:触发-setValue:forUndefinedKey:方法(默认会抛出一个异常)
赋值时,value必须是对象类型,如果不是对象类型,编译器会报错。如果value传nil会怎么样呢?我们来测试一下:
[prod setValue:nil forKey:@"name"]; //成功赋值
NSLog(@"name = %@",[prod valueForKey:@"name"]);
[prod setValue:nil forKey:@"price"]; //崩溃
NSLog(@"price = %@",[prod valueForKey:@"price"]);
[prod setValue:nil forKey:@"size"]; //崩溃
NSLog(@"size = %@",[prod valueForKey:@"size"]);
在以上实验中,price 和 size 的都会抛出'NSInvalidArgumentException', reason: '[<Product 0x6000007bae80> setNilValueForKey]: could not set nil as the value for the key
,如果在Product
类中实现setNilValueForKey:
方法,就不会崩溃,由此我们得出结论:
当value值为nil时,如果属性的数据类型为对象类型,则会把nil赋值给对应的属性;如果属性的数据类型为基本数据类型时,则会触发
setNilValueForKey:
方法 ,如果该方法没重写,则会抛出异常。
还有一点值得注意:KVC可以访问私有成员变量,可以通过accessInstanceVariablesDirectly
方法禁用对私有成员变量的访问
3、keyPath方式使用KVC
通过知识点1和2,我们可以实现对属性的赋值和取值操作,但是对于多层级的属性的取值/赋值操作就比较麻烦了。比如:
Product类包含一个Factory(工厂)类型的属性,描述了商品的产地信息,我们要获取
prod
对象的生产地址,该怎么办呢?
一种写法是这样的:通过 Factory *factory = [prod valueForKey:@"factory"]
然后再调用 NSString *address = [factory valueForKey:@"address"]
获取地址。这种方法能够实现我们的功能,但是比较笨拙。KVC体统了一种更简单的方式:keyPath(关键路径):将要访问属性的层级关系一一连接起来,用.
分割,即可组成关键路径。例子中的关键路径可以写为:@"factory.address"
。调用KVC的: -(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
和(nullable id)valueForKeyPath:(NSString *)keyPath;
方法集合实现对属性的赋值,取值操作。代码如下:
[prod setValue:@"北京市海淀区西北旺百度大厦" forKeyPath:@"factory.address"];
NSString *address = [prod valueForKeyPath:@"factory.address"];
NSLog(@"生产地址:%@",address);
四、小结
KVC提供了一套通过字符串访问属性,私有成员变量的方式。在类的声明不透明的情况下,可以通过这种方式进行取值、赋值操作。对私有变量的访问要看accessInstanceVariablesDirectly
返回值是YES还是NO,YES标识允许,NO表示不允许。keyPath在多层嵌套的属性访问时,更为方便。
网友评论