一、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文章:点击打开链接