美文网首页
OC由浅入深系列 之 KVC:(一)基本用法

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

作者: SimonMont | 来源:发表于2019-03-01 00:33 被阅读0次

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/zfrluqtx.html