Objective-C KVC

一、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:
The optional methods can improve performance of the proxy object.

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]

Objective-C  KVC

Objective-C  KVC


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


Objective-C KVC

上一篇:Mysql 数据库备份脚本


下一篇:详细总结 Hive VS 传统关系型数据库