本文属于原创,转载请注明出处
参考文章:
APPLE官方文档
Key Value-Coding简介
KVC是由NSKeyValueCoding正式启用的一种机制,对象可以用该协议实现对属性的间接访问,当一个对象与KVC兼容,它的属性可以通过一个简洁统一的消息传递接口通过字符串参数寻址,这种间接的访问机制提供了一种通过Asscessor Methods来获取实例变量的方法
通常使用Asscessor Methods来访问对象的属性,一个 get 类型的 Assocessor Methods返回一个属性的值,一个 set 类型的 Assocessor Methods 设置一个属性的值,在Objective-C中可以获取属性通过实例变量,这样获取属性是更直接的,但这需要调用一个属性明确的方法或变量名,随着属性列表的增长和更改,访问的变量也需要随着一起变化。相反,一个与KVC的对象提供一种简单的消息传递接口,该接口在其所有属性中都是一致的,KVC是COCOA中很多技术的基础,比如KVO,CoreData,有时KVC也可以使代码更明确
使用Key Value-Coding兼容的对象
当对象继承自NSObject时就兼容KVC,符合NSKeyValueCoding协议并提供一系列方法
- 获取对象属性
- 改变集合属性
- 获取没有对象的属性
- 使用Key Path获取属性
对象采用Key Value-Coding编码
使用KVC要确保符合NSKeyValueCoding协议
- valueForKey: 是getter,setValue: forKey是一个setter
Swift的Key Value-Coding
Swift中所有属性都是NSObject的子类
获取对象的属性
属性一般分为以下几类
- 属性:普通属性,如CGFloat,NSString
- 对应关系的属性:这种属性往往有自己的属性,其属性的值可以改变,但不会改变自身
- 一对多关系的属性:集合对象,NSArray,Collection等
@interface LNBankAccount: NSObject
@property (nonatomic) NSNumber *currentBalance;
@property (nonatomic) Person *owner;
@property (nonatomic) NSArray< Transaction > *transactions;
@end
为了封装属性,对象通常会提供相应的访问方法来访问属性,比如设置方法(setter)
[myAccount setCurrentBalance: @(100.0)];
这是直接的,但是缺乏灵活性,一个可以使用KVC的对象提供一种更通用的方法获取属性,使用String的标识符
标示一个对象的属性使用Keys和Key Paths
一个key是标示了特定的属性的字符串
[myCount setValue: @(100.0) forKey: "currentBalance"];
// 因为上面的对象是KVC兼容的,所以可以使用其变量名访问属性
取值
NSNumber *myCount = [blank valueForKey: @"currentBalance"];
KVC实战简化列表UI
@interface Contact: NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *city;
@end
在控制器里,有四个对应的UITextField属性
@interface DetailViewController()
@property (weak, nonatomic) IBOutlet UITextField *nameField;
@property (weak, nonatomic) IBOutlet UITextField *nicknameField;
@property (weak, nonatomic) IBOutlet UITextField *emailField;
@property (weak, nonatomic) IBOutlet UITextField *cityField;
@end
首先要两个方法,一个返回所有的键,一个将键映射到对应的文本框方法
- (NSArray *) contactStringKeys
{
return @[@"name", @"nickname", @"email", @"city"];
}
- (UITextField *) textFieldForModeKey: (NSString *) key
{
return [self valueForKey: [key stringByAppendingString:@"Feild"];
}
这样就可以从model里更新文本框
- (void)updateTextFields
{
for (NSString *key in self.contactStringsKeys) {
[self textFieldForMode: key].text = [self.contact valueForKey: key];
}
}
也可以使用一个action方法让四个文本框都能实时更新Model
- (IBAction)fieldEditingDidEnd: (UITextFiled *) sender
{
for (NSString *key in self.contactStringKeys) {
UITextField *field = [self textFieldForModeForKey: key];
if (field == sender) {
[self.contact setValue:sender.text forKey: key];
break;
}
}
}
Key Path键路径
KVC同样支持使用关系来访问对象,使用valueForKeyPath:
[person valueForKeyPath: @"address.city"];
Key Value-Coding不用@Property实现属性
这样使用当标量和struct被传入nil的时候要特别注意
- (CGFloat) height;
- (void)setHeight:(CGFloat)height;
[object setValue:nil forKey: @"height"];
这样会抛出异常,使用时要处理nil的情况,要覆写setNilValueForKey:
- (void) setNilValueForKey: (NSString *) key
{
if ([key isEqualToString: @"height"]) {
[self setValue: @0 forKey: key];
} else {
[super setNilValueForKey: key];
}
}
通过覆写方法来让类支持KVC,不过这样会影响性能
集合操作
一个常被忽略的Key Value-Coding的操作是它对集合操作的支持,比如,可以这样来获得数组中的最大值
NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath: @"@max.self"]);
或者获取一个存储对象的数组中对象的一个属性的最大值
NSArray *a = @[transaction1, transaction2, transaction3];
NSLog("max = %@", [a valueForKeyPath:@"@max.amount"]);
当调用valueForKeyPath的时候,它会在数组中调用amount的最大值并返回