一、Key Value Coding(KVC)提供了一种间接访问对象属性(用字符串表征,作为key值)的机制
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
例如对于Person类:
@property(nonatomic,copy)NSString* name;//属性为类类型
@property(nonatomic)CGFloat height;//属性为标量类型
Person *person = [[Person alloc] init];
[person setValue:@"name" forKey:@"name"];
[person setValue:@(20) forKey:@"height"];
NSLog(@"name = %@",[person valueForKey:@"name"]);
NSLog(@"height = %@",[person valueForKey:@"height"]);
注意到假如 Person 类有一个地址属性 address,地址 address 又有一个属性 city。
那么要访问Person类对象的 city 这个属性,就得通过Key Path,即使用下面的方法:
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
[person valueForKeyPath:@"address.city"]
二、Key-Value Coding Without @property
通过 key-value coding-compliant,可以取代 @property 和 @synthesize,通过实现 -<key>: 和 -set<key>: 方法即可。
例如对于属性 name,我们需要实现
- (NSString *)name;
- (void)setName:(NSString *)name;
但是,对于数值型的标量和struct值,例如: height
- (CGFloat)height;
- (void)setHeight:(CGFloat)height;
如果:
[object setValue:nil forKey:@"height"]
运行时,将会出错:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<Person 0x100108bf0> setNilValueForKey]: could not set nil as the value for the key height.'
为了,解决 nil 值的问题,我们需要重载实现- (void)setNilValueForKey:(NSString *)key 方法:
-(void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:@"height"]) {
[self setValue:@0 forKey:key];
}
else
{
[super setNilValueForKey:key];
}
}
另外,可以通过实现下面两个方法来动态支持某些key值:
- (id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
三、Collection Operators
这是个经常被忽视的KVC特性。
例如:
NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);
可以求得数组的最大值。
又例如:某一类 Transaction,有一个 amount 属性,我们可以求出 Transaction 类对象中 amount 的最大值。
NSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);
调用 [a valueForKeyPath:@"@max.amount"] ,实际上对数组中的每一个 Transaction 对象元素都调用了 -valueForKey:@"amount"
然后返回最大值。
对于 Collection Operator 官方文档中有详细的介绍:点击打开链接
四、KVC Through Collection Proxy Objects(比较少用到)
对于不可变集合类型:
When we call -valueForKey:
on an object, that object can return collection proxy objects for an NSArray
, an NSSet
, or an NSOrderedSet
. The class doesn’t implement the normal -<Key>
method but instead implements a number of methods that the proxy uses.
例如:对于一个数组类型的属性 contracts 需要实现:
- (NSUInteger)countOfContacts;
- (id)objectInContactsAtIndex:(NSUInteger)idx;
对于NSArray,NSSet,NSOrderedSet,需要实现的方法如下:
NSArray | NSSet | NSOrderedSet |
---|---|---|
-countOf<Key> | -countOf<Key> | -countOf<Key> |
-enumeratorOf<Key> | -indexIn<Key>OfObject: | |
One of | -memberOf<Key>: | |
-objectIn<Key>AtIndex: | One of | |
-<key>AtIndexes: | -objectIn<Key>AtIndex: | |
-<key>AtIndexes: | ||
Optional (performance) | ||
-get<Key>:range: | Optional (performance) | |
-get<Key>:range: |
Using these proxy objects only makes sense in special situations, but in those cases it can be very helpful. Imagine that we have a very large existing data structure and the caller doesn’t need to access all elements (at once).
对于可变集合类型:NSMutableArray
,NSMutableSet
,NSMutableOrderedSet
.
获取这些可变集合类型,可以调用如下的方法:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
例如:
- (NSMutableArray *)mutableContacts;
{
return [self mutableArrayValueForKey:@"wrappedContacts"];
}
对于 NSMutableArray,NSMutableSet,NSMutableOrderedSet 需要实现的方法如下:
NSMutableArray / NSMutableOrderedSet | NSMutableSet |
---|---|
At least 1 insertion and 1 removal method | At least 1 addition and 1 removal method |
-insertObject:in<Key>AtIndex: | -add<Key>Object: |
-removeObjectFrom<Key>AtIndex: | -remove<Key>Object: |
-insert<Key>:atIndexes: | -add<Key>: |
-remove<Key>AtIndexes: | -remove<Key>: |
Optional (performance) one of | Optional (performance) |
-replaceObjectIn<Key>AtIndex:withObject: | -intersect<Key>: |
-replace<Key>AtIndexes:with<Key>: | -set<Key>: |
五、常见的KVO错误
(1)We cannot observe an NSArray; we can only observe a property on an object – and that property may be an NSArray.
As an example, if we have a ContactList object, we can observe itscontacts property, but we cannot pass an NSArray to -addObserver:forKeyPath:... as the object to be observed.
(2)Observing self doesn’t always work. It’s probably not a good design pattern, either.
六、KVO调试
(lldb) po [observedObject observationInfo]
七、Key-Value Validation(校验)
对于某些模型对象,需要校验其中的属性,那么通过在Key-Value Validation ,实现相关的校验方法,就可以在模型类中实现key-value校验。
例如:需要对 Contract 模型中的 name 属性进行校验:
- (BOOL)validateName:(NSString **)nameP error:(NSError * __autoreleasing *)error
{
if (*nameP == nil) {
*nameP = @"";
return YES;
} else {
*nameP = [*nameP stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
return YES;
}
}
那么使用时:
- (IBAction)nameFieldEditingDidEnd:(UITextField *)sender;
{
NSString *name = [sender text];
NSError *error = nil;
if ([self.contact validateName:&name error:&error]) {
self.contact.name = name;
} else {
// Present the error to the user
}
sender.text = self.contact.name;
}
更详细内容请见objc.io文章:点击打开链接