Key Value Coding and Key Value Observing
Key Value Coding1
简介
KVC(Key-value coding)是一套利用字符串标识符间接访问对象属性和关系的机制,由NSKeyValueCoding informal protocal
定义。
事实上,KVO定义了NSObject中所需要实现的accessor的符号访问方式和方法名(也就是Dot syntex
访问方式和-set<key>
、-is<key>
等obj accessor方法名命名方式)。在Cocoa框架中,Core Data,Application Scriptability2, 绑定(Cocoa Binding)技术和KVO语言特性等,都是以KVC为基础的。
PS: Application Scriptability 和 绑定(Cocoa Binding)是Mac OS X上特有的)。其中Application Scriptability借助KVC来访问和设置model obj[下图]中的数据:
Cocoa框架中,访问obj实例里面的变量需要用到class中定义的accessor方法,包括getter和setter
你也可以用KVC来简化你的代码。
下面两例子使用OS X中 NSTableView
/ NSOutLineView
绑定行列数据方法的具体实现作对比:
Listing 1 Implementation of data-source method without key-value coding
- (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.
}
Listing 2 Implementation of data-source method with key-value coding
- (id)tableView:(NSTableView *)tableview
objectValueForTableColumn:(id)column row:(NSInteger)row {
ChildObject *child = [childrenArray objectAtIndex:row];
return [child valueForKey:[column identifier]];
}
一些基础知识
Model(M) 3
IOS应用开发是遵循MVC设计模式的,Cocoa框架用Object Modeling的规则来规范一个Model的实现。
Object Modeling有如下几个概念的规定:
Entity:表示持有数据的一个实体
Property:实体中的成员,分为Attribute和:Relationship
Attribute:基本类型的成员,比如:数字、NSString。
Relationship:指向其它Entity的关系型成员,它又有to 1Relationship和to manyRelationship的区别。
attributes, to-one relationships, and to-many relationships
- ####attributes
不可变的简单变量,如字符串、各种标量、Cocoa框架中的NSValue
int counter = 0;
NSTimeInterval interval = 24*60*60;
- ####to-one relationships
obj与其property的一对一关系。property,也就是写在interface中得@property
,如下方的name、author和publicDate。比起上面的attributes,这些变量所保存的值可以在与其所属obj不变的情况下改变。⬅️这句听起来好别扭……
@interface QCTBook : NSObject
@property NSString *name;
@property NSString *author;
@property NSDate *publicDate;
@end
- ####to-many relationships
一对多关系。各种Collection类型,如有序的NSArray、NSMutableArray,无序的NSSet,NSMutableSet、NSDictionary、NSMutableDictionary
@interface QCTBookself : NSObject
@property NSMutableArray *bookList;
@end
//main
QCTBook *qctBook = [QCTBook new];
QCTBOOkself *qctBookself = [QCTBookself new];
qctBookself.boolList.add(qctBook);
Keys and Key Paths
Key
Key 是用来标识指定property的字符串。一般来说,Key对应到obj的一个accessor方法名或者实例变量名。Key遵循ACSII编码,小写字母开头且不能包含空格。如上面例子中QCTBook obj中的name、author。
Key Path
Key Path是由Key个逗号分隔符组成用来标识obj中property得路径。如上面例子中QCTBookself obj中的bookList.name、bookList.author。
Value
Get
valueForKey:、valueForKeyPath:、dictionaryWithValuesForKeys:
Set
setValue:forKey:、setValue:forKeyPath:、setValuesForKeysWithDictionary:、mutableArrayValueForKey:、mutableSetValueForKey:.
Dot Syntex and Key-Value Coding
例子:
You can use key-value coding methods to access a property, for example, given a class defined as follows:
@interface MyClass
@property NSString *stringProperty;
@property NSInteger integerProperty;
@property MyClass *linkedInstance;
@end
you could access the properties in an instance using KVC:
MyClass *myInstance = [[MyClass alloc] init];
NSString *string = [myInstance valueForKey:@"stringProperty"];
[myInstance setValue:@2 forKey:@"integerProperty"];
这是对比:
To illustrate the difference between the properties dot syntax and KVC key paths, consider the following.
MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
myInstance.linkedInstance.integerProperty = 2;
This has the same result as:
MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
[myInstance setValue:@2 forKeyPath:@"linkedInstance.integerProperty"];
Key-Value Coding Accessor Methods
为了让valueForKey:
、 setValue:forKey:
、 mutableArrayValueForKey:
和 mutableSetValueForKey:
等方法生效,需要实现KVC accessor 方法:
本章的accessor pattern是以这样的形式出现:
-set<Key>:
、-<key>
。其中key是具体property名。还要注意和 的区别在于首字母是否大写。
Commonly Used Accessor Patterns
返回property:-<key>
。具体可以是object、scalar、data structure。而-is<Key>
返回BOOL。
栗子:
使用-<key>
:
- (BOOL)hidden{
//Implementation specific code.
return ...;
}
使用-is<Key>
:
- (BOOL) isHidden {
//Implementation specific code.
return ...;
}
为了实现setValue:forKey:
,还需要有下面方法:
- (void)setHidden:(BOOL)flag{
//Implementation specific code.
return ...;
}
对于不是object的情况,需要额外地定义setNilValueForKey:
方法设置的值:
- (void)setNilValueForKey:(NSString *)theKey {
if ([theKey isEqualToString:@"hidden"]) {
[self setValue:@YES forKey:@"hidden"];
} else {
[super setNilValueForKey:theKey];
}
}
Collection Accessor Patterns for To-Many Properties
虽然你可以使用-<key>
、-set<Key>:
来设置to-many relationship,但是你应该只在创建collection object的时候才使用他们。为了管理to-many relationship,最好实现针对他们的accessor methods。
好处:
- 处理to-many relationship的时候性能提升大;
- 假装成NSArray、NSSet
- 通过KVO来实现对to-meny relationship property的直接修改
两种collection accessor,分为order和unordered。
Indexed Accessor Pattern
包含方法:
- counting
- retrieving
- adding
- replacing
实现这些方法之后可以让property用起来像NSArray一样。
如果是NSArray、NSSet直接返回值不写也可以。
返回mutable array方法:mutableArrayValueForKey:
。
Getter Indexed Accessors
getter方法如下:
-countOf<Key>
必要-objectIn<Key>AtIndex:
、-<key>AtIndexs:
其中一个。 必要,对应到NSArray的objectAtIndex:
、objectsAtIndexes
-get<Key>:range:
可选,对应NSArray的getObjects:range:
。可以提升性能。
一些具体的栗子:
- (NSUInteger)countOfEmployees {
return [self.employees count];
}
- (id)objectInEmployeesAtIndex:(NSUInteger)index {
return [employees objectAtIndex:index];
}
- (NSArray *)employeesAtIndexes:(NSIndexSet *)indexes {
return [self.employees objectsAtIndexes:indexes];
}
- (void)getEmployees:(Employee * __unsafe_unretained *)buffer range:(NSRange)inRange {
// Return the objects in the specified range in the provided buffer.
// For example, if the employees were stored in an underlying NSArray
[self.employees getObjects:buffer range:inRange];
}
Mutable Indexed Accessors
可以直接实现:
mutableArrayValueForKey:
或者实现这些方法:
-insertObject:in<Key>AtIndex:
、-insert<Key>:atIndexes:
其中一个。对应NSArray的insertOjbect:atIndex:
、insertObjects:atIndexes:
。-removeObjectFrom<Key>AtIndex:
、-remove<Key>AtIndexes:
其中一个。对应NSArray中的removeObjectAtIndex:
和removeObjectsAtIndexes:
。-replaceObjectIn<Key>AtIndex:withObject:
、replace<Key>AtIndexes:with<Key>
可选。可以提升性能。
具体栗子:
- (void)insertObject:(Employee *)employee inEmployeesAtIndex:(NSUInteger)index {
[self.employees insertObject:employee atIndex:index];
return;
}
- (void)insertEmployees:(NSArray *)employeeArray atIndexes:(NSIndexSet *)indexes{
[self.employees insertObjects:employeeArray atIndexes:indexes];
return;
}
- (void)removeObjectFromEmployeesAtIndex:(NSUInteger)index {
[self.employees removeObjectAtIndex:index];
}
- (void)removeEmployeesAtIndexes:(NSIndexSet *)indexes {
[self.employees removeObjectsAtIndexes:indexes];
}
- (void)replaceObjectInEmployeesAtIndex:(NSUInteger)index withObject:(id)anObject {
[self.employees replaceObjectAtIndex:index withObject:anObject];
}
- (void)replaceEmployeesAtIndexes:(NSIndexSet *)indexes withEmployees:(NSArray *)employeeArray {
[self.employees replaceObjectsAtIndexes:indexes withObjects:employeeArray];
}
Undered Accessor Pattern
NSSet、NSMutableSet
Getter Unordered Accessors
和上面的差不多,只是无序:
-countOf<Key>
必要。对应NSSet的count
方法-enumeratorOf<Key>
必要。对应NSSet的objectEnumerator
menberOf<Key>
必要。对应NSSet的member
栗子:
- (NSUInteger)countOfTransactions {
return [self.transactions count];
}
- (NSEnumerator *)enumeratorOfTransactions {
return [self.transactions objectEnumerator];
}
- (Transaction *)memberOfTransactions:(Transaction *)anObject {
return [self.transactions member:anObject];
}
Mutable Unordered Accessors
实现mutableSetValueForKey:
直接返回
或者:
-add<Key>Object:
、-add<Key>:
其中一个。必要。对应NSMutableSet中的addObject:
。-remove<Key>Object:
、-remove<Key>:
其中一个。必要。对应NSMutableSet的removeObject:
。-intersect<Key>:
可选。对应NSSet中intersectSet:
。
栗子:
- (void)addTransactionsObject:(Transaction *)anObject {
[self.transactions addObject:anObject];
}
- (void)addTransactions:(NSSet *)manyObjects {
[self.transactions unionSet:manyObjects];
}
- (void)removeTransactionsObject:(Transaction *)anObject {
[self.transactions removeObject:anObject];
}
- (void)removeTransactions:(NSSet *)manyObjects {
[self.transactions minusSet:manyObjects];
}
- (void)intersectTransactions:(NSSet *)otherObjects {
return [self.transactions intersectSet:otherObjects];
}
Key-Value Validation
KVO 提供用于做验证的API
Validation Method Naming Convention
格式:validate<Key>:error:
栗子:一个传入参数和一个错误传出地址,返回验证结果BOOL
- (BOOL) validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError {
//Implementation specific code.
return ...;
}
Implementing a Validation Method
三种判断结果:
- 验证通过返回YES,NSError为空。
- 传入值验证不通过返回NO,NSError描述验证失败原因。
- 创建一个新值并返回YES,NSError为空。不鼓励这么做。
栗子:
-(BOOL)validateName:(id *) error:(NSError * __autoreleaseing *)outError{
//The name must not be nil, and must be at least two characters long.
if ((*ioValue == nil) || ([(NSString *)ioValue length] < 2)){
if (outError != NULL){
NSString *errorString = NSLocaloizedString(@"A Person's name must be at least two characters long",@"validation:Person, too short name error");
NSDictionary *userInfoDict = @{NSLocalizedDescriptionKey:errorString};
*outError = [[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN code:PERSON_INVALID_NAME_CODE userInfo:userInfoDict];
}
return NO;
}
return YES;
}
注意要检查传入的NSError是否非空。
Invoking Validation Methods
调用方法:validateValue:forKey:error:
,该函数会根据key搜索对应的validate<Key>:eoor:
方法并调用。如果对应方法不存在的话直接返回YES。
不能在
-set<Key>:
里面调用验证方法。
Automatic Validation
使用Cocoa Binding 可以实现自动调用验证方法,但是只有OS X才能用。
Validation of Scalar Values
non-object自动封装成NSValue、NSNumber。
- (BOOL) validateAge:(id *)ioValue error:(NSError * _autoreleasing *)outError{
if (ioValue == nil){
//Trap this in setNilValueForKey.
//an alertnative might be to create new NSNumber with value 0 here.
return YES;
}
if ([*ioValue floatValue] <= 0.0){
if (outError != NULL){
NSString *errorString = NSLocalizedFromTable(@"",@"");
NSDictionary *userInfoDict = @{NSLocalizedDescriptionKey: errorString};
NSError *error = [[NSError alloc] initWithDomian:PERSON_ERROR_DOMIAN code:PERSON_INVALID_AGE_CODE userInfo:userInfoDict]];
}
}else{
return YES'
}
// ...
}
Ensuring KVC Compliance
保证一个类的KVO约束正常所需要完成的步骤:
Attribute and To-One Relationship Compliance
- 实现
-<key>
、-is<Key>
或者有实例变量<key>
、_<key>
。对于URL,KVC允许key大写。 - 如果property是可修改的,实现
-set<Ket>:
。 -set<Key>
实现不能包含验证步骤。- 如果需要验证的话实现
validate<Key>:error:
。
Indexed To-Many Relationship Compliance
- 实现可以返回整个数值的
-<key>
。 - 或者有直接有一个实例变量
<key>
、_<key>
。 - 或者实现
-countOf<Key>
和-objectIn<Key>AtIndex:
、-<key>AtIndexes:
。 - 可选。实现
get<Key>:range:
来提升性能。
mutable的话还要:
- 实现
-insertObject"in<Key>AtIndex:"
或者-insert<Key>:atIndexes:
。 - 实现
-removeObjectFrom<Key>AtIndexes:
或者-remove<Key>AtIndexes:
。 - 可选。实现
replaceObjectIn<Key>AtIndex:withObject:
或者-replace<Key>AtIndexes:with<Key>:
来提升性能。
Unordered To-Many Relationship Compliance
- 实现方法
-<key>
来返回set。 - 或者设置一个实例变量
<key>
或者_<key>
。 - 或者实现
-countOf<Key>
和-enumeratorOf<key>
、-memberOf<Key>
。
mutable情况:
- 实现至少一个:
-add<Key>Ojbect:
、-add<Key>:
。 - 实现至少一个:
-remove<Key>Object:
、-remove<Key>:
。 - 可选。实现
-intersect<Key>:
、-set<Key>:
提升性能。
Scalar and Structure Support
KVC 提供对non-object类型数据的自动封装(NSValue、NSNumber)
Represent Non-Object Values
valueForKey:
如果结果值不是一个object,将创建NSValue、NSNumber来封装后返回。
setValue:forKey:
如果赋值对象不是object会自动调用传入object的-<type>Value
来转换或再赋值。
Handing nil Values
由于不能对non-object赋值nil,需要重写-setNilValueForKey:
方法来阻止默认抛出NSInvalidArgumentException
:
- (void)setNilValueForKey:(NSString *)theKey{
if ([theKey isEqualToString:@"age"]){
[self setValue[NSNumber numberWithFloat:0.0] forKey:@"age"];
}else{
[super setNilValueForKey:theKey];
}
}
对于一些二进制数据的写入可能会需要重写
-unableToSetNilForKey:
Wrapping and Unwrapping Scaler Types
标量类型,使用NSNumber封装。
记一下:
Data type | Creationg method | Accessor method |
---|---|---|
BOOL | numberWithBool: | boolValue |
char | numberWithChar: | charValue |
double | numberWithDouble: | doubleValue |
float | numberWithFloat: | floatValue |
int | numberWithInt: | intValue |
long | numberWithLong: | longValue |
long long | numberWithLongLong: | longLongValue |
short | numberWithShort: | shortValue |
unsigned char | numberWithUnsingedCahr: | unsignedChar |
unsigned int | numberWithUnsignedInt: | unsignedInt |
unsigned long | numberWithUnsignedLong: | unsignedLong |
unsigned long long | numberWirhUsignedLongLong: | unsignedLongLong |
unsigned short | numberWithUsgnedShort: | unsgnedShort |
Wrapping and Unwrapping Structs
结构体类型,使用NSValue封装。
Data type | Creation method | Accessor method |
---|---|---|
NSPoint | valueWithPoint: | pointValue |
NSRange | valueWithRange: | rangeValue |
NSRect | valueWithRect:(OS X only) | rectValue |
NSSize | valueWithSize: | sizeValue |
栗子:
typedef struct {
float x,y,z;
} ThreeFloats
@interface MyClass
//sending an instance of MyClass the message valueForKey: with the parameter @"threeFloats" will invoke the MyClass method threeFloats and return the result wrapped in an NSValue.
- (void)setThreeFloats:(ThreeFloats)theFloats;
//Sending the instance of MyClass a setValue:forKey: message with an NSValue object wrapping a ThreeFloats struct will invoke setThreeFloats: and pass the result of sending the NSValue object getValue: message.
- (ThreeFloats)threeFloats;
@end
该机制并不负责MRR……