iOS - KVC

[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时:

  1. 首先根据方法名setValue:forKey找到运行方法所需要的SEL。

  2. 再根据对象的isa指针, 结合SEL, 找到具体的方法实现IMP。

  3. 调用方法实现完成KVC赋值


搜索方式

如何访问属性值

setValue:forKey: 搜索方式
iOS - KVC_第1张图片
image

注: accessInstanceVariablesDirectly 该类方法的默认返回值为YES

  1. 首先按顺序搜索setKey:_setKey: 方法(key指成员变量名, 首字母大写); 若找到即调用。

  2. 若上面2个setter方法都没找到, 如果类方法 accessInstanceVariablesDirectly 返回YES。那么按 _key_isKeykeyiskey 的顺序搜索成员名。

  3. 如果没有找到成员变量, 调用 setValue:forUnderfinedKey: 方法的默认抛出异常, 我们可以根据需要重写它


valueForKey: 搜索方式
iOS - KVC_第2张图片
image
  1. 首先按 getKeykeyisKeykey 的顺序查找getter方法, 找到直接调用。如果是 BOOLint 等内建值类型, 会做NSNumber的转换。
  2. 若上面的getter没找到, 查找 countOfKeyobjectInKeyAtindexKeyAtindexes 格式的方法。如果countOfKey和另外两个方法中的一个找到, 那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法。
  3. 还没找到, 查找 countOfKeyenumeratorOfKeymemberOfKey 格式的方法。如果这三个方法都找到, 那么就返回一个可以响应NSSet所有方法的代理集合。
  4. 还是没找到, 如果类方法 accessInstanceVariablesDirectly 返回YES。那么按 _key_isKeykeyiskey 的顺序搜索成员名。
  5. 再没找到, 调用 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会自动将标量值 (intfloat等) 翻入NSNumberNSValue 中包装成一个对象, 然后返回。也就是说, KVC有自动包装功能。

  • 而使用 setValue:forKey: 是相当于自动调用了 NSNumber 的属性 intValue/floatValue, 取出具体值赋值给成员变量; 所以 KVC 可以直接将 NSNumber 赋值给基本数据类型的成员变量。


你可能感兴趣的:(iOS - KVC)