KVC之定义集合方法

当你使用这篇文章描述的标准约定创建一个访问方法和ivar时,KVC协议的默认实现可以根据KVC消息定位它们。对于表示to-many关系的集合对象来说也是如此。但是,如果你实现了集合访问方法,我们就可以:

  • 与除了NSArray或NSSet以外的类建立to-many关系.当我们实现了集合方法,key-value getter的默认实现是返回一个代理对象,该对象调用这些方法以响应后续收到的NSArrayNSSet消息。属性对象不必是NSArray或者NSSet,因为代理对象使用集合方法提供预期的行为。

  • 改变to-many关系的内容可以提高性能. 协议的默认实现是使用你的集合方法来改变基础属性,而不是重复创建新的集合对象。

  • 为集合属性中的内容提供KVO访问 更多内容可以查看 Key-Value Observing Programming Guide.

你可以实现两类集合访问方法中的一个,具体取决于我们希望关系的行为像索引的有序集合(如NSArray对象)还是无序的、但唯一的集合(如NSSet对象)。任何情况下,需要至少实现一组方法来支持对属性的读取访问。

KVC协议不会声明在这部分描述的的方法。相反,NSObject提供的协议的默认实现是在你的KVC兼容对象中查找这些方法,如KVC方法的搜索模式所述,并使用它们来处理部分KVC消息。

访问有序集合

你可以添加索引访问方法,以提供计算、检索、添加和替换有序关系中的对象的机制。底层对象通常是NSArrayNSMutableArray对象,但是如果为对象提供了集合访问方法,我们可以像处理数组一样处理这些属性。

有序集合的getters

对于没有默认的getter的集合属性。如果你提供以下索引集合方法,则对于valueForKey:消息,协议的默认实现是返回一个代理对象,其行为类似NSArray,但是会调用以下集合方法以完成它的工作。

在现在的OC中,编译器为每个属性默认生成了getter,因此默认实现不会创建使用本节的方法。可以通过不声明属性(仅依靠一个ivar),或者你可以使用@dynamic声明一个属性来解决这个问题,这表明我们计划在运行时提供访问者行为。无论何种方式,编译器都不会提供默认的getter,而默认的实现会使用下面的方法

  • countOf<Key>

    此方法以NSInteger 的形式返回to-many 关系中对象的个数,就像NSArraycount方法一样。事实上,当底层属性是NSArray时,可以使用该方法提供结果。

    - (NSUInteger)countOfTransactions {
        return [self.transactions count];
    }
    
  • objectIn<Key>AtIndex: or <key>AtIndexes:

    第一个方法返回 to-many 关系中指定下标中的对象,第二个返回一个NSIndexSet参数指定索引处的对象数组。 它们分别对应于NSArrayobjectAtIndex:objectsAtIndexes:。 只需要实现它们中的一个。transactions数组的相应方法是:

    - (id)objectInTransactionsAtIndex:(NSUInteger)index {
    return [self.transactions objectAtIndex:index];
    }
    
    - (NSArray *)transactionsAtIndexes:(NSIndexSet *)indexes {
    return [self.transactions objectsAtIndexes:indexes];
    }
    
  • get<Key>:range:

    该方法是可选的,但可以提高性能。它返回集合指定范围内的对象,对应于NSArraygetObjects: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: or insert<Key>:atIndexes:

    第一个方法接收要插入的对象和指定应该插入的位置。第二个方法插入一个对象数组到NSIndexSet指定的索引处的集合中。这跟NSMutableArrayinsertObject: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: or remove<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>:

    为代理对象提供了一种直接替换集合中对象的方法,而无需连续删除一个对象并插入另一个对象。对应于NSMutableArrayreplaceObjectAtIndex: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];
    }
    

访问无序集合

通常,这种关系是NSSetNSMutableSet对象的一个实例。 但是,实现这些方法时,我们可以使用键值编码对该对象进行操作,就像它是NSSet的实例一样。

无序集合的getter

当你提供以下集合方法以返回集合中对象的数量的时候,valueForKey:消息返回一个跟NSSet行为类似的代理对象,但是是调用下面的集合方法来完成。

  • countOf<Key>

    该必需方法返回关系中条目的数量,对应NSSetcount方法。当底层对象是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: or add<Key>:

添加单个或多个对象。当添加多个对象时,请确保关系中不存在同等的对象。这与NSMutableSetaddObject:unionSet:方法类似。 只需要其中一个方法:

 - (void)addEmployeesObject:(Employee *)anObject {
 [self.employees addObject:anObject];
 }

 - (void)addEmployees:(NSSet *)manyObjects {
 [self.employees unionSet:manyObjects];
 }
  • remove<Key>Object: or remove<Key>:

    从关系中删除单个或者多个对象。这与NSMutableSetremoveObject:minusSet:类似。 只需要其中一个方法:

    - (void)removeEmployeesObject:(Employee *)anObject {
     [self.employees removeObject:anObject];
    }
    
    - (void)removeEmployees:(NSSet *)manyObjects {
     [self.employees minusSet:manyObjects];
    }
    
  • intersect<Key>:

    这个方法接收一个NSSet作为参数,从关系中删除不为输入集与集合中共有的所有对象。这跟NSMutableSetintersectSet:方法等效。该方法是可选的。

    - (void)intersectEmployees:(NSSet *)otherObjects {
      return [self.employees intersectSet:otherObjects];
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容