KVO通知:修改由NSMutableArray支持的NSArray

10

我想使用KVO监听一个NSArray属性的集合变化事件。在公开范围内,该属性是只读的NSArray,但是由于需要修改集合,它是由一个NSMutableArray实例变量来支持的。

我知道我可以将属性设置为新值以获取“set”更改,但我对添加、删除、替换更改很感兴趣。如何正确地通知这些类型的NSArray更改?

@interface Model : NSObject

@property (nonatomic, readonly) NSArray *items;

@end

@implementation Model {
    NSMutableArray *_items;
}

- (NSArray *)items {
    return [_items copy];
}

- (void)addItem:(Item *)item {
  [_items addObject:item];
}

@end
Model *model = [[Model alloc] init];

[observer addObserver:model 
      forKeyPath:@"items" 
         options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) 
         context:NULL];

Item *item = [[Item alloc] init];
[model addItem:newItem];

观察者类:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"items"]) {
        //Not called
    }
}

你能发一下你已经写的代码吗? - Holly
此外,您可能应该查看这篇文章 - Holly
重复的问题似乎将可变数组放在头文件中,有没有办法在不公开访问要修改的数组的情况下完成这个任务? - Kevin DiTraglia
еҸҰдёҖдёӘй—®йўҳдёӯivarеңЁеӨҙж–Ү件дёӯзҡ„е”ҜдёҖеҺҹеӣ жҳҜд»Јз Ғж—©дәҺ@implementation ivarsгҖӮи§ЈеҶіж–№жЎҲзҡ„ж ёеҝғжҳҜзҙўеј•и®ҝй—®еҷЁж–№жі•гҖӮиҜ·жіЁж„ҸPeter Hoseyзҡ„зӯ”жЎҲ - жӮЁдёҚеә”иҜҘе…¬ејҖж•°з»„гҖӮ - jscs
@JoshCaswell 我的问题核心是如何观察公共NSArray的更改,该数组由私有NSMutableArray支持。在提出这个问题之前,我阅读了重复的问题,但它没有回答我的问题,即如何连接KVO以观察对数组的更改,而不暴露内部可变数组。 - Kevin DiTraglia
此外,每个答案似乎都在暗示使用NSArrayController,但它在iOS中不可用。 - Kevin DiTraglia
1个回答

20

首先,你应该了解KVO是用于观察对象的属性变化的。也就是说,你不能"观察一个数组",而是要观察一个索引集合属性。这个属性可能由一个数组支持或以其他方式实现。只要它符合KVC并以符合KVO的方式修改,那就足够了。 (因此,属性是NSArray *类型或使用NSMutableArray *实现或任何其他类型都无关紧要。)

所以,你正在观察Model实例中其items属性的变化。如果你希望观察者收到变更通知,你必须确保始终以符合KVO的方式修改items属性。

在我看来,最好的方法是实现可变索引集合访问器(mutalbe indexed collection accessors),并始终使用这些方法来修改属性。因此,你至少需要实现其中一个:

- (void) insertObject:(id)anObject inItemsAtIndex:(NSUInteger)index;
- (void) insertItems:(NSArray *)objects atIndexes:(NSIndexSet *)indexes;

还有其中的一个:

- (void) removeObjectFromItemsAtIndex:(NSUInteger)index;
- (void) removeItemsAtIndexes:(NSIndexSet *)indexes;

如果属性由NSMutableArray支持,那么上述方法只是对_items相应方法的简单封装。

你编写的任何其他修改属性的方法都应该通过其中之一。因此,你的-addItem:方法应该如下所示:

- (void)addItem:(Item *)item {
    [self insertObject:item inItemsAtIndex:[_items count]];
}

您还可以删除 items 属性的普通 getter,而仅公开索引集合 getter:

- (NSUInteger) countOfItems;
- (id) objectInItemsAtIndex:(NSUInteger)index;

如果有一个典型的getter方法存在的话,这并不是必需的。

(这些属性的访问器(accessor)的存在使您能够实现一个不是NSArray类型的to-many属性。从KVC的角度来看,没有任何实际的数组类型接口是必需的。)

个人而言,我不建议这样做,但是一旦您拥有这样的访问器,您还可以通过使用-mutableArrayValueForKey:获取属性的类似于NSMutableArray的代理并向其发送变异操作来改变属性。所以,在这种情况下,您可能会执行[[self mutableArrayValueForKey:@"items"] addObject:item]。我不喜欢这样做,因为我觉得键值编码是用于键是数据时才使用的。它是动态的或存储在数据文件中,例如 NIB,而不是在编译时已知。当您可以使用语言符号(例如选择器)来访问属性时,硬编码键名称是一种代码气味。

但是,如果某些操作在索引访问器方面真正难以实现,比如排序,那么这样做是可以被证明是合理的。

最后,您可以使用NSKeyValueObserving协议的-willChange...-didChange...方法,在直接修改属性的后备存储而不经过KVO可以识别并钩入的变异方法时发出更改通知。对于索引集合属性,这将是-willChange:valuesAtIndexes:forKey:-didChange:valuesAtIndexes:forKey:方法。就我个人而言,这甚至是更糟糕的代码气味。


非常棒的回答,感谢您的解释,我已经接近答案了,但这让我更加明确了它。 - Kevin DiTraglia
6
我刚刚完成了一个 KVOMutableArray(https://github.com/haifengkao/KVOMutableArray),它实现了 @Ken 的想法。 - Hai Feng Kao
@HaiFengKao 为什么不将你的 KVOMutableArray 子类化为 NSMutableArray 呢? - ChenYilong
@stevechen KVC仅适用于getter和setter。如果我子类化NSMutableArray,就不会有任何可观察的getter和setter。 - Hai Feng Kao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接