KVC和KVO是Objective-C中经常被提到的两个术语。这篇文章将解释KVC和KVO的定义和最基本的用法,更多的信息请移步结尾部分的官方文档。
KVC(Key Value Coding)
定义
KVC的全称是Key Value Coding。这个名字会让人想到map的操作方式。实际上也正是这样,KVC可以让我们把NSObject对象当作一个map来用,即用字符串(Key)来访问NSObject对象的属性(Value)。
用法
我们只要使用Objective-C的property机制来定义成员变量,就可以使用KVC来访问属性了:
@interface BankAccount : NSObject
@property (nonatomic, assign) NSInteger balance;
@end
@interface Person : NSObject
- (instancetype)initWithName:(NSString*)name andBalance:(NSInteger)balance;
@property (nonatomic, strong) BankAccount *account;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSArray* friends;
@end
对于支持KVC的类,我们可以使用以下四个方法访问属性:
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
直接看例子:
- (void) testKVC {
// 初始化
Person *zhangSan = [[Person alloc] initWithName:@"ZhangSan" andBalance:20];
zhangSan.friends = @[@"LiSi", @"WangWu"];
// 读取属性
NSLog(@"%@", [zhangSan valueForKey:@"name"]); // ZhangSan
// 名字包含KeyPath的方法支持用点语法访问属性的属性。
// 注意,数值型的属性会自动被封包为NSNumber。
NSLog(@"%@", [zhangSan valueForKeyPath:@"account.balance"]); // 20
// 同样,支持点语法设置属性。
[zhangSan setValue:@150 forKeyPath:@"account.balance"];
// 支持NSArray和NSDictionary这类代表一对多含义的属性
NSLog(@"%@", [zhangSan valueForKey:@"friends"]); // 输出[LiSi, WangWu]
}
用途
因为系统提供的类都支持KVC,所以我们可以用KVC的方式来设置SDK未暴露出来的属性,比如修改UITextField的占位符的颜色。(这种做法依赖了系统的内部的实现,应该尽量避免):
[self.textfield setValue:[UIColor greyColor] forKeyPath:@"_placeholderLabel.textColor"];
我们还可以让控件的标识和数据对象的属性完全一样,使用KVC就可以节省大量的判断逻辑。下面是官方文档里的例子:
// 不用KVC
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row {
ChildObject *child = [childrenArray objectAtIndex:row];
if ([[column identifier] isEqualToString:@"name"]) {
return [child name];
}
if ([[column identifier] isEqualToString:@"age"]) {
return [child age];
}
if ([[column identifier] isEqualToString:@"favoriteColor"]) {
return [child favoriteColor];
}
// And so on.
}
// 用KVC
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row {
ChildObject *child = [childrenArray objectAtIndex:row];
return [child valueForKey:[column identifier]];
}
更多的情况下,KVC是被用作别的技术的基础,比如下面的KVO。
KVO(Key Value Observing)
定义
KVO的全称是Key Value Observing。它是一种基于KVC的观察者模式的实现。如果不用KVO,我们要让一个类的属性实现观察者模式,得给这个类加上AddObserver/RemoveObserver方法,得在这个类里加一个数组保存所有的Observers,得在属性值变化的时候通知所有的Observer。虽然逻辑简单,但总归得折腾一会。
而有了KVO,我们可以使用很少的代码用上观察者模式了。
用法
@interface BankAccount : NSObject
@property (nonatomic, assign) NSInteger balance;
@end
@interface Person : NSObject
@property (nonatomic, strong) BankAccount *account;
@end
@implementation Person
- (instancetype)init {
...
// 注册Observer:
[self.account addObserver:self
forKeyPath:@"balance"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld
context:nil];
...
}
- (void)dealloc {
// 不要忘了removeObserver
[self.account removeObserver:self forKeyPath:@"balance"];
}
// 属性变化的回调方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"balance"]) {
NSLog(@"Balance was %@.", change[NSKeyValueChangeOldKey]);
NSLog(@"Balance is %@ now.", change[NSKeyValueChangeNewKey]);
}
}
@end
- (void)testKVO {
Person *zhangSan = [[Person alloc] initWithName:@"ZhangSan" andBalance:20];
// 无论是用点语法还是KVC的方法都会触发回调:
zhangSan.account.balance = 150;
[zhangSan setValue:@250 forKeyPath:@"account.balance"];
}
参考文档
Key-Value Coding Programming Guide
Key-Value Observing Programming Guide