当你使用这篇文章描述的标准约定创建一个访问方法和ivar
时,KVC协议的默认实现可以根据KVC消息定位它们。对于表示to-many
关系的集合对象来说也是如此。但是,如果你实现了集合访问方法,我们就可以:
与除了NSArray或NSSet以外的类建立
to-many
关系.当我们实现了集合方法,key-value getter
的默认实现是返回一个代理对象,该对象调用这些方法以响应后续收到的NSArray
或NSSet
消息。属性对象不必是NSArray
或者NSSet
,因为代理对象使用集合方法提供预期的行为。改变
to-many
关系的内容可以提高性能. 协议的默认实现是使用你的集合方法来改变基础属性,而不是重复创建新的集合对象。为集合属性中的内容提供KVO访问 更多内容可以查看 Key-Value Observing Programming Guide.
你可以实现两类集合访问方法中的一个,具体取决于我们希望关系的行为像索引的有序集合(如NSArray对象)还是无序的、但唯一的集合(如NSSet对象)。任何情况下,需要至少实现一组方法来支持对属性的读取访问。
KVC协议不会声明在这部分描述的的方法。相反,NSObject提供的协议的默认实现是在你的KVC兼容对象中查找这些方法,如KVC方法的搜索模式所述,并使用它们来处理部分KVC消息。
访问有序集合
你可以添加索引访问方法,以提供计算、检索、添加和替换有序关系中的对象的机制。底层对象通常是NSArray
或 NSMutableArray
对象,但是如果为对象提供了集合访问方法,我们可以像处理数组一样处理这些属性。
有序集合的getters
对于没有默认的getter的集合属性。如果你提供以下索引集合方法,则对于valueForKey:
消息,协议的默认实现是返回一个代理对象,其行为类似NSArray
,但是会调用以下集合方法以完成它的工作。
在现在的OC中,编译器为每个属性默认生成了getter,因此默认实现不会创建使用本节的方法。可以通过不声明属性(仅依靠一个ivar),或者你可以使用
@dynamic
声明一个属性来解决这个问题,这表明我们计划在运行时提供访问者行为。无论何种方式,编译器都不会提供默认的getter,而默认的实现会使用下面的方法
-
countOf<Key>
此方法以NSInteger 的形式返回
to-many
关系中对象的个数,就像NSArray
的count
方法一样。事实上,当底层属性是NSArray
时,可以使用该方法提供结果。- (NSUInteger)countOfTransactions { return [self.transactions count]; }
-
objectIn<Key>AtIndex:
or<key>AtIndexes:
第一个方法返回
to-many
关系中指定下标中的对象,第二个返回一个NSIndexSet
参数指定索引处的对象数组。 它们分别对应于NSArray
的objectAtIndex:
和objectsAtIndexes:
。 只需要实现它们中的一个。transactions数组的相应方法是:- (id)objectInTransactionsAtIndex:(NSUInteger)index { return [self.transactions objectAtIndex:index]; } - (NSArray *)transactionsAtIndexes:(NSIndexSet *)indexes { return [self.transactions objectsAtIndexes:indexes]; }
-
get<Key>:range:
该方法是可选的,但可以提高性能。它返回集合指定范围内的对象,对应于
NSArray
的getObjects:range:
方法,transactions数组的相应方法是:- (void)getTransactions:(Transaction * __unsafe_unretained *)buffer range:(NSRange)inRange { [self.transactions getObjects:buffer range:inRange]; }
比如在一个Person
类中,定义了一个gender
属性(NSString
对象),这里我们在.m
文件为其实现相应的集合方法,这样gender
就会表现的跟一个数组一样。
@interface Person : NSObject
@property (nonatomic , copy) NSString *gender;
@end
@implementation Person
// 使用@dynamic修饰,所以编译器不会为其生成getter和setter
@dynamic gender;
- (NSUInteger)countOfGender
{
return 5;
}
- (id)objectInGenderAtIndex:(NSUInteger)index
{
return @"w";
}
@end
id obj = [person valueForKey:@"gender"];
NSString *cls = NSStringFromClass([obj class]);
NSLog(@"gender: %@", obj);
NSLog(@"className:%@",cls);
打印信息为
gender: (
w,
w,
w,
w,
w
)
className:NSKeyValueArray
可以看出,虽然我们声明的是NSString
,但由于我们实现了属性相应的集合方法,这里属性已经变成了NSArray
类型。(而且都是不可变数组,即使我们声明的是NSMutableArray
类型)
有序集合的改变
使用索引方法来实现可变的 to-many
的关系必须实现一组不同的方法。当你提供这些setter方法时,mutableArrayValueForKey:
消息的默认实现是返回一个与NSMutableArray
行为相似的代理对象。这比直接返回一个NSMutableArray
对象更加有效率。
为了使键值编码对象表现跟可变、有序的 to-many
关系一样,需要实现以下这些方法:
-
insertObject:in<Key>AtIndex:
orinsert<Key>:atIndexes:
第一个方法接收要插入的对象和指定应该插入的位置。第二个方法插入一个对象数组到
NSIndexSet
指定的索引处的集合中。这跟NSMutableArray
的insertObject:atIndex:
和insertObjects:atIndexes:
方法类似。只需要其中一个方法。- (void)insertObject:(Transaction *)transaction inTransactionsAtIndex:(NSUInteger)index { [self.transactions insertObject:transaction atIndex:index]; } - (void)insertTransactions:(NSArray *)transactionArray atIndexes:(NSIndexSet *)indexes { [self.transactions insertObjects:transactionArray atIndexes:indexes]; }
-
removeObjectFrom<Key>AtIndex:
orremove<Key>AtIndexes:
第一个接收一个
NSUInteger
值,第二个接收一个NSIndexSet
对象,指定要删除的对象的索引。 这些方法分别对应于NSMutableArray
方法removeObjectAtIndex:
和removeObjectsAtIndexes:
。 只需要其中一种方法。- (void)removeObjectFromTransactionsAtIndex:(NSUInteger)index { [self.transactions removeObjectAtIndex:index]; } - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes { [self.transactions removeObjectsAtIndexes:indexes]; }
-
replaceObjectIn<Key>AtIndex:withObject:
或replace<Key>AtIndexes:with<Key>:
为代理对象提供了一种直接替换集合中对象的方法,而无需连续删除一个对象并插入另一个对象。对应于
NSMutableArray
的replaceObjectAtIndex:withObject:
和replaceObjectsAtIndexes:withObjects:
方法。当应用程序分析显示性能问题时,可以选择提供这些方法。- (void)replaceObjectInTransactionsAtIndex:(NSUInteger)index withObject:(id)anObject { [self.transactions replaceObjectAtIndex:index withObject:anObject]; } - (void)replaceTransactionsAtIndexes:(NSIndexSet *)indexes withTransactions:(NSArray *)transactionArray { [self.transactions replaceObjectsAtIndexes:indexes withObjects:transactionArray]; }
访问无序集合
通常,这种关系是NSSet
或NSMutableSet
对象的一个实例。 但是,实现这些方法时,我们可以使用键值编码对该对象进行操作,就像它是NSSet
的实例一样。
无序集合的getter
当你提供以下集合方法以返回集合中对象的数量的时候,valueForKey:
消息返回一个跟NSSet
行为类似的代理对象,但是是调用下面的集合方法来完成。
-
countOf<Key>
该必需方法返回关系中条目的数量,对应
NSSet
的count
方法。当底层对象是NSSet
对象,会直接调用count
方法。比如- (NSUInteger)countOfEmployees { return [self.employees count]; }
-
enumeratorOf<Key>
这个必需方法返回一个
NSEnumerator
实例,用于遍历关系中的条目。- (NSEnumerator *)enumeratorOfEmployees { return [self.employees objectEnumerator]; }
-
memberOf<Key>:
这个方法将作为参数传过来的对象和集合中的内容进行比较,并返回匹配的对象,如果没有找到匹配的对象返回
nil
。如果要手动实现比较方法,通常是使用isEqual:
来比较对象。如果底层对象是NSSet
对象,可以使用member:
方法- (Employee *)memberOfEmployees:(Employee *)anObject { return [self.employees member:anObject]; }
无序集合的改变
为了使可变无序的to-many
关系的支持键值编码,需要实现以下方法:
-
add<Key>Object:
oradd<Key>:
添加单个或多个对象。当添加多个对象时,请确保关系中不存在同等的对象。这与NSMutableSet
的addObject:
和unionSet:
方法类似。 只需要其中一个方法:
- (void)addEmployeesObject:(Employee *)anObject {
[self.employees addObject:anObject];
}
- (void)addEmployees:(NSSet *)manyObjects {
[self.employees unionSet:manyObjects];
}
-
remove<Key>Object:
orremove<Key>:
从关系中删除单个或者多个对象。这与
NSMutableSet
的removeObject:
和minusSet:
类似。 只需要其中一个方法:- (void)removeEmployeesObject:(Employee *)anObject { [self.employees removeObject:anObject]; } - (void)removeEmployees:(NSSet *)manyObjects { [self.employees minusSet:manyObjects]; }
-
intersect<Key>:
这个方法接收一个
NSSet
作为参数,从关系中删除不为输入集与集合中共有的所有对象。这跟NSMutableSet
的intersectSet:
方法等效。该方法是可选的。- (void)intersectEmployees:(NSSet *)otherObjects { return [self.employees intersectSet:otherObjects]; }