KVC, KVO 作为一种魔法贯穿日常Cocoa开发,笔者原先是准备写一篇对其的全面总结,可网络上对其的表面介绍已经够多了,除去基本层面的使用,笔者跟大家谈下平常在网络上没有提及的KVC, KVO进阶知识。旨在分享交流。
KVC的消息传递
valueForKey:
的使用并不仅仅用来取值那么简单,还有很多特殊的用法,集合类也覆盖了这个方法,通过调用valueForKey:
给容器中每一个对象发送操作消息,并且结果会被保存在一个新的容器中返回,这样我们能很方便地利用一个容器对象创建另一个容器对象。另外,valueForKeyPath:还能实现多个消息的传递。一个例子:
1 2 3 4 5 6 7 8 9 10 |
NSArray *array = [NSArray arrayWithObject:@"10.11", @"20.22", nil]; NSArray *resultArray = [array valueForKeyPath:@"doubleValue.intValue"]; NSLog(@"%@", resultArray); //打印结果 ( 10, 20 ) |
KVC容器操作
容器不仅仅能使用KVC方法实现对容器成员传递普通的操作消息,KVC还定义了特殊的一些常用操作,使用valueForKeyPath:
结合操作符
来使用,所定义的keyPath格式入下图所示 Left key path:如果有,则代表需要操作的对象路径(相对于调用者)
Collection operator:以”@”开头的操作符
Right key path:指定被操作的属性
常规操作符:
- @avg、@count、@max、@min、@sum
对象操作符:
- @distinctUnionOfObjects、@unionOfObjects
1 |
NSArray *values = [object valueForKeyPath:@"@unionOfObjects.value"]; |
@distinctUnionOfObjects操作符返回被操作对象指定属性的集合并做去重操作,而@unionOfObjects则允许重复。如果其中任何涉及的对象为nil,则抛出异常。
Array和Set操作符:
Array和Set操作符操作对象是嵌套型的集合对象
- @distinctUnionOfArrays、@unionOfArrays
1 |
NSArray *values = [arrayOfobjectsArrays valueForKeyPath:@"@distinctUnionOfArrays.value"]; |
同样的,返回被操作集合下的集合中的对象的指定属性的集合,并且做去重操作,而@unionOfObjects则允许重复。如果其中任何涉及的对象为nil,则抛出异常。
- @distinctUnionOfSets
1 |
NSSet *values = [setOfobjectsSets valueForKeyPath:@"@distinctUnionOfSets.value"]; |
返回结果同理于NSArray。
据官方文档说明,目前还不支持自动以操作符。
KVC与容器类(集合代理对象)
当然对象的属性可以是一对一的,也可以是一对多。属性的一对多关系其实就是一种对容器类的映射。如果有一个名为numbers的数组属性,我们可以使用valueForKey:@"numbers"
来获取,这个是没问题的,但KVC还能使用更灵活的方式管理集合。——集合代理对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
ElfinsArray.h @interface ElfinsArray : NSObject @property (assign ,nonatomic) NSUInteger count; - (NSUInteger)countOfElfins; - (id)objectInElfinsAtIndex:(NSUInteger)index; @end ElfinsArray.m #import "ElfinsArray.h" @implementation ElfinsArray - (NSUInteger)countOfElfins { return self.count; } - (id)objectInElfinsAtIndex:(NSUInteger)index { return [NSString stringWithFormat:@"小精灵%lu", (unsigned long)index]; } @end Main.m - (void)work { ElfinsArray *elfinsArr = [ElfinsArray alloc] init]; elfinsArr.count = 3; NSArray *elfins = [ElfinsArray valueForKey:@"elfins"]; //elfins为KVC代理数组 NSLog(@"%@", elfins); //打印结果 ( "小精灵0", "小精灵1", "小精灵2" ) } |
问题来了,ElfinsArray中并没有定义elfins属性,那么elfins数组从何而来?valueForKey:
有如下的搜索规则:
- 按顺序搜索getVal、val、isVal,第一个被找到的会用作返回。
- countOfVal,或者objectInValAtIndex:与valAtIndexes其中之一,这个组合会使KVC返回一个代理数组。
- countOfVal、enumeratorOfVal、memberOfVal。这个组合会使KVC返回一个代理集合。
- 名为val、isVal、val、isVal的实例变量。到这一步时,KVC会直接访问实例变量,而这种访问操作破坏了封装性,我们应该尽量避免,这可以通过重写+accessInstanceVariablesDirectly返回NO来避免这种行为。
ok上例中我们实现了第二条中的特殊命名函数组合:
1 2 |
- (NSUInteger)countOfElfins; - (id)objectInElfinsAtIndex:(NSUInteger)index; |
这使得我们调用valueForKey:@"elfins"
时,KVC会为我们返回一个可以响应NSArray所有方法的代理数组对象(NSKeyValueArray),这是NSArray的子类,- (NSUInteger)countOfElfins
决定了这个代理数组的容量,- (id)objectInElfinsAtIndex:(NSUInteger)index
决定了代理数组的内容。本例中使用的key是elfins,同理的如果key叫human,KVC就会去寻找-countOfHuman:
可变容器呢
当然我们也可以在可变集合(NSMutableArray、NSMutableSet、NSMutableOrderedSet)中使用集合代理:
这个例子我们不再使用KVC给我们生成代理数组,因为我们是通过KVC拿到的,而不能主动去操作它(insert/remove),我们声明一个可变数组属性elfins。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
ElfinsArray.h @interface ElfinsArray : NSObject @property (strong ,nonatomic) NSMutableArray *elfins; - (void)insertObject:(id)object inNumbersAtIndex:(NSUInteger)index; - (void)removeObjectFromNumbersAtIndex:(NSUInteger)index; @end ElfinsArray.m #import "ElfinsArray.h" @implementation ElfinsArray - (void)insertObject:(id)object inElfinsAtIndex:(NSUInteger)index { [self.elfins insertObject:object atIndex:index]; NSLog(@"insert %@n", object); } - (void)removeObjectFromElfinsAtIndex:(NSUInteger)index { [self.elfins removeObjectAtIndex:index]; NSLog(@"removen"); } @end Main.m - (void)work { ElfinsArray *elfinsArr = [ElfinsArray alloc] init]; elfinsArr.elfins = [NSMutableArray array]; NSMutableArray *delegateElfins = [ElfinsArray mutableArrayValueForKey:@"elfins"]; //delegateElfins为KVC代理可变数组,非指向elfinsArr.elfins [delegateElfins insertObject:@"小精灵10" atIndex:0]; NSLog(@"first log n %@", elfinsArr.elfins); [delegateElfins removeObjectAtIndex:0]; NSLog(@"second log n %@", elfinsArr.elfins); //打印结果 insert 小精灵10 first log ( "小精灵10" ) remove second log ( ) } |
上例中,我们通过调用
1 2 3 |
- mutableArrayValueForKey: - mutableSetValueForKey: - mutableOrderedSetValueForKey: |
KVC会给我们返回一个代理可变容器delegateElfins,通过对代理可变容器的操作,KVC会自动调用合适KVC方法(如下):
1 2 3 4 5 |
//至少实现一个insert方法和一个remove方法 - insertObject:inValAtIndex: - removeObjectFromValAtIndex: - insertVal:atIndexes: - removeValAtIndexes: |
间接地对被代理对象操作。
还有一组更强大的方法供参考
1 2 |
- replaceObjectInValAtIndex:withObject: - replaceValAtIndexes:withVal: |