美文网首页
KVC探讨-设定值 setValue: forKey:和取值va

KVC探讨-设定值 setValue: forKey:和取值va

作者: GitArtOS | 来源:发表于2020-03-01 21:53 被阅读0次

    KVC (Key-Value Coding)利用 NSKeyValueCoding 非正式协议实现的一种机制,来达到间接访问属性的目的;这样就允许我们在运行时去动态地访问和修改对象的属性, 而不是在编译时决定。

    KVC的定义是通过对 NSObject的扩展来实现的。所以对于所有集成了 NSObject的类来说都可以使用 KVC, 也就是说除了少数类型 (结构体) 以外都可以使用KVC

    • 常用方法
    - (void)setValue:(nullable id)value forKey:(NSString *)key;// 通过 key 设值
    - (nullable id)valueForKey:(NSString *)key;// 通过 key 取值
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;// 通过 keyPath 设值
    - (nullable id)valueForKeyPath:(NSString *)keyPath;// 通过 keyPath 取值
    
    
    • NSKeyValueCoding类别的其它方法:
    // 默认为YES。 如果返回为YES,如果没有找到 set<Key> 方法的话, 会按照_key, _isKey, key, isKey的顺序搜索成员变量, 返回NO则不会搜索
    + (BOOL)accessInstanceVariablesDirectly;
    // 键值验证, 可以通过该方法检验键值的正确性, 然后做出相应的处理
    - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
    // 如果key不存在, 并且没有搜索到和key有关的字段, 会调用此方法, 默认抛出异常。两个方法分别对应 get 和 set 的情况
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    // setValue方法传 nil 时调用的方法
    // 注意文档说明: 当且仅当 NSNumber 和 NSValue 类型时才会调用此方法 
    - (void)setNilValueForKey:(NSString *)key;
    // 一组 key对应的value, 将其转成字典返回, 可用于将 Model 转成字典
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    

    1. 设定值过程 setValue: forKey:

      1. 程序会去优先调用, set<Key>:或者_set<Key>, setIs<Key> 方法, 如果存在这些命名规则的方法, 会直接调用该方法进行赋值。调用优先顺序按照上面书写的顺序。
        说明: 这里的 "key" 指成员变量名字, 书写格式需要符合 KVC 的命名规则。
    1. 如果没有找到步骤1的方法, 程序会回去判断 + (BOOL)accessInstanceVariablesDirectly; 方法的返回值, 如果该方法返回值为NO (默认为 YES, 在我们重写该方法时有可能返回NO, 一般不会返回NO), 则会执行setValue: forUndefinedKey:方法报错。
    1. 如果上一步方法的返回值为YES, 程序会去查找命名方式为 _<key>, _<isKey>, <key>, <isKey>形式的实例变量, 加入存在该形式的实例变量, 则会直接将我们调用方法的值赋值给该实例变量。这里的查找优先顺序也会按照书写顺序去查找。
    1. 如果第三步没有查找到符合规则的实例变量, 程序就会去执行 setValue: forUndefinedKey: 方法进行报错。

    大家可以用下面代码去验证:注释掉各个方法去验证

    // 声明实例变量
    @interface GTPerson : NSObject {
        @public
        NSString *_name;
        NSString *name;
        NSString *_isName;
        NSString *isName;
    }
    @end
    
    // .m文件实现上面提到的方法进行监听
    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    
    - (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key {
        NSLog(@"设置出现异常!!!");
    }
    
    - (void)setName:(NSString *)value {
        NSLog(@"%s - %@", __func__, value);
    }
    - (void)setIsName:(NSString *)value {
        NSLog(@"%s - %@", __func__, value);
    }
    - (void)_setName:(NSString *)value {
        NSLog(@"%s - %@", __func__, value);
    }
    
    // 测试代码
        GTPerson *person = [[GTPerson alloc] init];
        [person setValue:@"person" forKey:@"name"];
      
        NSLog(@"%@ - %@ - %@ - %@", person->_name, person->_isName, person->name, person->isName);
        NSLog(@"%@ - %@ - %@", person->_isName, person->name, person->isName);
        NSLog(@"%@ - %@", person->name, person->isName);
        NSLog(@"%@", person->isName);
    
    

    2.kvc的取值会调用ValueForKey:但是其对值的搜索过程不同于setValue:forKey:

    1. 首先按照 get<Key>, <key>, is<Key>, _<key> 的顺序查找方法, 如果找到方法, 执行找到的方法得到返回值, 返回值的判断跳到第5步; 如果没有查找到方法, 进行下一步
    1. 如果没有找到上面的方法, KVC 就会去继续查找 countOf<Key>, objectIn<Key>AtIndex: (对应NSArray的方法), <key>AtIndexes: (对应NSArray 的 objectsAtIndexes: 方法) 格式的方法, 如果找到countOf<Key>另外两个方法中的一个, 就会返回一个可以响应所有NSArray方法的代理集合对象。当该代理集合对象接收到 NSArray 的方法调用时, 会去转换为对countOf<Key>, objectIn<Key>AtIndex 或 <Key>AtIndexes 这几个方法的调用 (此外还有一个可选方法格式为 get<Key>:range )。(注意: 该类为NSKeyValueArray , 是NSArray的子类)
      (判断是否属于NSArrray,基于是否找到NSArray相关的实例方法)

    2. 如果第2步仍然没有找到, 就会继续去查找 countOf<Key>, enumeratorOf<Key>, 和 memberOf<Key>: (对应NSSet的方法) 格式的方法, 如果这三种格式的方法都找到, 就会返回一个响应所有NSSet方法的代理集合对象, 反之则进行第4步。该集合对象会将接收到的NSSet方法调用转换为对 countOf<Key>, enumeratorOf<Key>, 和 memberOf<Key>: 方法的调用。
      (判断是否属于NSSet,基于是否有NSSet相关的方法)

    3. 如果没有找到任何符合要求的方法, 然后 accessInstanceVariablesDirectly的返回值为YES, 会像上面的设值过程一样去查询实例变量_<key>, _is<Key>, <key>, 或 is<Key>,如果查询到符合条件的实例变量, 会直接取出实例变量的值, 然后进行第5步。反之, 直接到第6步。

    1. 如果第4步获取到的属性值是一个对象指针, 直接返回结果也就是说( 检索属性值,如果是指针对象,直接返回结果); 如果该值是NSNumber 支持的标量类型, 将其存储为 NSNumber类型的实例然后返回; 如果该值不是NSNumber 支持的标量类型, 将其转换为NSValue对象然后返回。
    1. 调用valueForUndefinedKey:方法进行报错。

    验证代码:

    // 
    @property (nonatomic,strong) NSArray *array;
    @property (nonatomic,strong) NSSet *set;
    
    @property (nonatomic,strong) NSMutableString *arrayM;
    @property (nonatomic,strong) NSMutableSet *setM;
    @property (nonatomic,strong) NSMutableOrderedSet *orderSetM;
    
    //
    - (id)valueForUndefinedKey:(NSString *)key {
        NSLog(@"取值出现异常!!!");
        return key;
    }
    
    //
    - (NSString *)getName {
        return NSStringFromSelector(_cmd);
    }
    - (NSString *)name {
        return NSStringFromSelector(_cmd);
    }
    - (NSString *)isName {
        return NSStringFromSelector(_cmd);
    }
    - (NSString *)_name {
        return NSStringFromSelector(_cmd);
    }
    
    #pragma - NSArray -
    - (NSUInteger)countOfPens {
        NSLog(@"- %s -", __func__);
        return [self.array count];
    }
    - (id)objectInPensAtIndex:(NSUInteger)index {
        NSLog(@"- %s -", __func__);
        return self.array[index];
    }
    
    #pragma - NSSet -
    - (NSUInteger)countOfBooks {
        NSLog(@"- %s -", __func__);
        return [self.set count];
    }
    - (NSEnumerator *)enumeratorOfBooks {
        NSLog(@"- %s -", __func__);
        return [self.set objectEnumerator];
    }
    - (NSString *)memberOfBooks:(NSString *)object {
        NSLog(@"- %s -", __func__);
        return [self.set containsObject:object] ? object : nil;
    }
    
    // 验证代码
        person->_name = @"我是 _name";
        person->name = @"我是 name";
        person->isName = @"我是 isName";
        person->_isName = @"我是 _isName";
        
        NSLog(@"***** %@", [person valueForKey:@"name"]);
        
        person.array = @[@"pen0", @"pen1", @"pen2", @"pen3", @"pen4"];
        NSArray *array = [person valueForKey:@"pens"];
        NSLog(@"%@", [array objectAtIndex:1]);
        NSLog(@"数量 %ld", [array count]);
        NSLog(@"是否存在该值 %d", [array containsObject:@"pen2"]);
    
        person.set = [NSSet setWithArray:person.array];
        NSSet *set = [person valueForKey:@"books"];
        [set enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop){
            NSLog(@"遍历set: %@", obj);
        }];
        NSLog(@"是否存在该值 %d", [set containsObject:@"pen2"]);
    
    

    上面的代码自己去验证,可以检索KVC在 valueForKey:@"name" 是的取值机制

    KVC探讨-操作数组和集合、字典探讨(二)
    KVC探讨-具体应用以及属性验证、异常处理(三)
    KVC探讨-自定义KVC(四)

    相关文章

      网友评论

          本文标题:KVC探讨-设定值 setValue: forKey:和取值va

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