[toc]
参考
KVC
KVO
http://www.jianshu.com/p/fbd1e7c93fd0
KVC
KVC (Key Value Coding 键值编码) 是一种可以通过字符串 (key) 来间接访问类属性的机制, 而不是通过直接调用 Setter、Getter 方法访问。
KVC 支持类对象和内建基本数据类型。
很多情况下可以简化程序代码。
KVC API
NSKeyValueCoding.h
KVC 关键方法定义在 NSKeyValueCoding.h
给NSObject添加的分类
@interface NSObject(NSKeyValueCoding)
@property (class, readonly) BOOL accessInstanceVariablesDirectly;
- (nullable id)valueForKey:(NSString *)key;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- (void)setNilValueForKey:(NSString *)key;
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
@end
给 NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet 添加的分类
@interface NSArray(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
@interface NSDictionary(NSKeyValueCoding)
- (nullable ObjectType)valueForKey:(NSString *)key;
@end
@interface NSMutableDictionary(NSKeyValueCoding)
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
@end
@interface NSOrderedSet(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)setValue:(nullable id)value forKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
@end
@interface NSSet(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
使用方法
获取值
valueForKey: // 传入NSString属性的名字。
valueForKeyPath: // 属性的路径, xx.xx
valueForUndefinedKey: // 默认实现是抛出异常, 可重写这个函数做错误处理
修改值
setValue:forKey: // 为对象的属性赋值, value的值必须是id,也就是说不能传基本数据类型, 必须是指针类型的变量
setValue:forKeyPath: // 包含了setValue:forKey:的功能, 并且还可为对象内的对象的属性赋值
setValuesForKeysWithDictionary: // 字典转模型, 对模型进行一次性赋值
注意点:
底层还是调用了setValue:forKey:
字典转模型的时候, 字典中的某一个key一定要在模型中有对应的属性
如果一个模型中包含了另外的模型对象, 是不能直接转化成功的。
通过KVC转化模型中的模型, 也是不能直接转化成功的
异常
setValue:forUnderfinedKey: // 默认实现是抛出异常, 可重写这个函数做错误处理
setNilValueForKey: // 对非类对象属性设置nil时调用, 默认抛出异常。
valueForUndefinedKey: // 访问了不存在的key
底层实现
KVC 实现分析:
[site setValue:@"sitename" forKey:@"name"];
// 上面的KVC代码会被编译器处理成下面三行:
SEL sel = sel_get_uid(setValue:forKey);
IMP method = objc_msg_loopup(site->isa, sel);
method(site, sel, @"sitename", @"name");
一个对象在调用setValue时:
首先根据方法名
setValue:forKey
找到运行方法所需要的SEL。再根据对象的isa指针, 结合SEL, 找到具体的方法实现IMP。
调用方法实现完成KVC赋值
搜索方式
如何访问属性值
setValue:forKey: 搜索方式
注:
accessInstanceVariablesDirectly
该类方法的默认返回值为YES
首先按顺序搜索
setKey:
和_setKey:
方法(key指成员变量名, 首字母大写); 若找到即调用。若上面2个
setter
方法都没找到, 如果类方法accessInstanceVariablesDirectly
返回YES。那么按_key
、_isKey
、key
、iskey
的顺序搜索成员名。如果没有找到成员变量, 调用
setValue:forUnderfinedKey:
方法的默认抛出异常, 我们可以根据需要重写它
valueForKey: 搜索方式
- 首先按
getKey
、key
、isKey
、key
的顺序查找getter
方法, 找到直接调用。如果是BOOL
、int
等内建值类型, 会做NSNumber
的转换。 - 若上面的
getter
没找到, 查找countOfKey
、objectInKeyAtindex
、KeyAtindexes
格式的方法。如果countOfKey
和另外两个方法中的一个找到, 那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法。 - 还没找到, 查找
countOfKey
、enumeratorOfKey
、memberOfKey
格式的方法。如果这三个方法都找到, 那么就返回一个可以响应NSSet所有方法的代理集合。 - 还是没找到, 如果类方法
accessInstanceVariablesDirectly
返回YES。那么按_key
、_isKey
、key
、iskey
的顺序搜索成员名。 - 再没找到, 调用
valueForUndefinedKey
。如果这个方法还是没有被实现的话, 程序会抛出一个NSUndefinedKeyException
异常错误。
KVC 在某种程度上提供了访问器的替代方案。因为访问器方法很优秀, 所以 KVC 尽可能使用访问器方法。
KVC 触发 KVO
KVC 触发 KVO 不依赖于setter方法。
通过 KVC 修改成员变量, 即便没有 setter 方法, 依然能够触发 KVO。
KVC 内部会通知 key 发生了改变, 猜测是调用了 willChangeValueForKey:
和 didChangeValueForKey:
, 重写这两个方法并添加打印代码, 可以看到kVC赋值时, 确实有被调用。
其中, didChangeValueForKey:
内部会触发KVO的监听回调方法 observeValueForKeyPath:ofObject:change:context:
, 完成KVO监听。具体参考《KVO》。
面试题
KVC 和 object_setIvar
为什么 KVC 可以用 NSNumber 来接收 int、float 的数据类型?
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
使用
valueForKey:
时, KVC会自动将标量值 (int
、float
等) 翻入NSNumber
或NSValue
中包装成一个对象, 然后返回。也就是说, KVC有自动包装功能。而使用
setValue:forKey:
是相当于自动调用了 NSNumber 的属性 intValue/floatValue, 取出具体值赋值给成员变量; 所以 KVC 可以直接将 NSNumber 赋值给基本数据类型的成员变量。