Cocoa数据绑定
MVC架构编程模式中,Controller负责将Model模型数据更新到View视图,同时当用户对视图View数据做了修改后,还需要Controller将变换的数据更新到Model模型中。
模型Model到视图View,视图View到模型Model,这种双向的数据更新涉及到大量繁琐的数据转换和赋值操作。因此OSX Cocoa从系统层面设计了Cocoa数据绑定机制,用以简化MVC编程中这种双向更新操作。
Cocoa数据绑定机制的实现依赖KVC,KVO,KVB 三种技术,下面分别来介绍说明。
KVC
KVC是Key-value coding 键值编码的简称,提供了通过类的属性字符名称来读写属性值的方法。你不需要通过调用类的不同的方法去实现不同的属性读写修改,而只需要通过不同的属性名称,通过统一一致的接口实现读写。
KVC的好处是明显的,可以实现循环遍历读写所有的属性值,也可以支持属性路径链式调用,即嵌套对象的访问。
Cocoa在基类NSObject上实现了KVC的接口方法,因此所有类天然支持KVC方式属性读写。
我们定义Person和Phone 2个类便于后面说明KVC的属性访问方式。
Person对象类,有3个属性:名字,年龄,电话。
@interface Person : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,strong)Phone *phone;
@end
电话对象类,有3个属性:办公电话,家庭电话,手机。
@interface Phone : NSObject
@property(nonatomic,strong)NSString *office;
@property(nonatomic,strong)NSString *family;
@property(nonatomic,strong)NSString *mobile;
@end
定义Person和Phone实例
Person *person= [[Person alloc]init];
person.name = @"john";
person.age = 20;
Phone *phone = [[Phone alloc]init];
phone.office = @"010-67854545";
person.phone = phone;
属性读写接口
1.通过名字访问对象属性值
-(id)valueForKey:(NSString *)key;
2.修改属性名为key的值为value
-(void)setValue:(id)value forKey:(NSString *)key;
//使用KVC方法获取属性
NSString *name = [person valueForKey:@"name"];
//使用KVC修改属性
[person setValue:@"Habo" forKey:@"name"];
路径访问接口
1.使用keyPath路径去获取属性
-(id)valueForKeyPath:(NSString *)keyPath;
2.更新keyPath路径对应的属性值为value
-(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
//使用path获取属性
NSString *officePhone = [person valueForKeyPath:@"phone.office"];
//修改属性
[person setValue:@"010-678545466" forKeyPath:@"phone.office"];
异常属性接口
1.访问不存在的属性,会调用此方法,如果此方法没有实现,会抛出异常。
-(id)valueForUndefinedKey:(NSString *)key;
2.修改不存在的属性值,会调用此方法,如果此方法没有实现,会抛出异常。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key;
3.修改属性值为nil,会调用此方法,如果此方法没有实现,会抛出异常
-(void)setNilValueForKey:(NSString *)key;
下面是用使用setValue方法对hidden属性修改为nil,默认设置为YES
- (void)setNilValueForKey:(NSString *)theKey {
if ([theKey isEqualToString:@"hidden"]) {
[self setValue:@YES forKey:@"hidden"];
} else {
[super setNilValueForKey:theKey];
}
}
4.修改key对应的value值,对value进行有效性验证。
对每个Key单独实现validate方法
-(BOOL)validate:(id *)ioValue error:(NSError * *)outError;
下面是对Person对象的age年龄进行validate的例子:
- (BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError
if ([*ioValue integerValue] <= 0) {
if (outError != NULL) {
NSString *errorString = NSLocalizedStringFromTable(@"Age must be greater than zero", @"Person",
@"validation: zero age error");
NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
NSError *error = [[NSError alloc] initWithDomain:@"" code:10005 userInfo:userInfoDict];
*outError = error;
};
return NO;
}
return YES;
}
对所有Key统一实现validate方法
-(BOOL)validateValue:(inout id )ioValue forKey:(NSString *)inKey error:(NSError **)outError;
-(BOOL)validateValue:(inout id )ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
在-set:方法中不要执行validation方法,如果需要对key的值修改进行有效性验证,在类中实现validation方法。
批量属性访问接口
1.访问多个key对应的属性,返回key-value形式的字典对象
-(NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
2.以字典对应的key-value去更新对象对应的属性。
-(void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
使用这个方法在网络处理JSON对象解析中可以方便通过字典对象来更新模型对象。
@interface Phone : NSObject
@property(nonatomic,strong)NSString *office;
@property(nonatomic,strong)NSString *family;
@property(nonatomic,strong)NSString *mobile;
-(id)initWithDictionary:(NSDictionary*)attributes;
@end
#import "Phone.h"
@implementation Phone
-(id)initWithDictionary:(NSDictionary*)attributes {
self = [super