OC由浅入深系列 之 KVC:(一)基本用法

2019-03-01  本文已影响0人  SimonMont

一、什么是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在多层嵌套的属性访问时,更为方便。

上一篇下一篇

猜你喜欢

热点阅读