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:
- 程序会去优先调用,
set<Key>:
或者_set<Key>
,setIs<Key>
方法, 如果存在这些命名规则的方法, 会直接调用该方法进行赋值。调用优先顺序按照上面书写的顺序。
说明: 这里的 "key" 指成员变量名字, 书写格式需要符合 KVC 的命名规则。
- 程序会去优先调用,
- 如果没有找到步骤1的方法, 程序会回去判断
+ (BOOL)accessInstanceVariablesDirectly
; 方法的返回值, 如果该方法返回值为NO (默认为 YES, 在我们重写该方法时有可能返回NO, 一般不会返回NO), 则会执行setValue: forUndefinedKey:
方法报错。
- 如果上一步方法的返回值为YES, 程序会去查找命名方式为
_<key>, _<isKey>, <key>, <isKey>
形式的实例变量, 加入存在该形式的实例变量, 则会直接将我们调用方法的值赋值给该实例变量。这里的查找优先顺序也会按照书写顺序去查找。
- 如果第三步没有查找到符合规则的实例变量, 程序就会去执行
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:
- 首先按照
get<Key>, <key>, is<Key>, _<key>
的顺序查找方法, 如果找到方法, 执行找到的方法得到返回值
, 返回值的判断跳到第5
步; 如果没有查找到方法
, 进行下一步
-
如果没有找到上面的方法, 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步仍然没有找到, 就会继续去查找 countOf<Key>, enumeratorOf<Key>, 和 memberOf<Key>: (对应NSSet的方法) 格式的方法, 如果这三种格式的方法都找到, 就会返回一个响应所有NSSet方法的代理集合对象, 反之则进行第4步。该集合对象会将接收到的NSSet方法调用转换为对 countOf<Key>, enumeratorOf<Key>, 和 memberOf<Key>: 方法的调用。
(判断是否属于NSSet,基于是否有NSSet相关的方法) -
如果没有找到任何符合要求的方法, 然后
accessInstanceVariablesDirectly
的返回值为YES, 会像上面的设值过程一样去查询实例变量_<key>, _is<Key>, <key>, 或 is<Key>
,如果查询到符合条件的实例变量, 会直接取出实例变量的值, 然后进行第5
步。反之, 直接到第6
步。
- 如果第
4
步获取到的属性值是一个对象指针, 直接返回结果也就是说( 检索属性值,如果是指针对象,直接返回结果); 如果该值是NSNumber
支持的标量类型, 将其存储为NSNumber
类型的实例然后返回; 如果该值不是NSNumber
支持的标量类型, 将其转换为NSValue
对象然后返回。
- 调用
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(四)
网友评论