使用KVO与NSNotificationCenter观察对可变阵列的更改

时间:2012-05-03 17:10:56

标签: ios model-view-controller delegates key-value-observing nsnotificationcenter

在我的模型中,我有一个名为events的对象数组。我希望无论何时将新对象添加到事件中,都会通知我的控制器。

我认为这样做的好方法是使用KVO模式在事件发生变化时收到通知(来自正在添加的新对象)

// AppDelegate
// events is a NSMutableArray @property/@synthesize etc...

[appDelagate addObserver:self
               forKeyPath:@"events"
                  options:NSKeyValueObservingOptionNew
                  context:NULL];

observeValueForKeyPath 方法未被调用,我发现Arrays不符合KVO: - (

一种选择是通过为keyPath调用 willChangeValueForKey 来手动触发方法

// ViewController
[self willChangeValueForKey:@"events"];
[self.events addObject:event];
[self didChangeValueForKey:@"events"];

但是这感觉很重,因为我应该跟踪事件数组的前后状态,以便可以从 observeValueForKeyPath 方法访问它。

一种方法可能是使用标准数组(而不是可变)并在每次要添加新对象时创建/设置新的事件实例,或者我可以创建一个单独的属性来跟踪有多少项在可变阵列中(我希望你能观察到@“events.count”)。

另一种选择是使用NSNotificationCenter。我还阅读了一些建议使用块的答案(但我不知道从哪里开始)。

最后,我可以将我的控制器实例保存在我的代理中并发送相关消息吗?

// Delegate
[myController eventsDidChange];

从委托中保留对控制器的引用是否很奇怪?

我很难理解如何选择最佳使用方法,因此非常感谢任何关于性能,未来代码灵活性和最佳实践的建议!

2 个答案:

答案 0 :(得分:18)

您不应为可变集合创建直接公共属性,以避免在您不知情的情况下进行变异。 NSArray本身不是键值观察,但您的一对多属性 @"events"是。以下是观察它的方法:

首先,声明一个不可变集合的公共属性:

@interface Model
@property (nonatomic, copy) NSArray *events;
@end

然后在你的实现中用一个可变的ivar:

@interface Model ()
{
    NSMutableArray *_events;
}
@end

并覆盖getter和setter:

@implementation Model

@synthesize events = _events;

- (NSArray *)events
{
    return [_events copy];
}

- (void)setEvents:(NSArray *)events
{
    if ([_events isEqualToArray:events] == NO)
    {
        _events = [events mutableCopy];
    }
}

@end

如果其他对象需要向模型添加事件,则可以通过调用-[Model mutableArrayValueForKey:@"events"]来获取可变代理对象。

NSMutableArray *events = [modelInstance mutableArrayValueForKey:@"events"];
[events addObject:newEvent];

这将通过每次使用新集合设置属性来触发KVO通知。为了获得更好的性能和更精细的控制,请实现array accessors的其余部分。

另请参阅:Observing an NSMutableArray for insertion/removal

答案 1 :(得分:0)

根据docs on accessor methods,您应该实现:

- (void)addEventsObject:(Event*)e
{
    [_events addObject:e];
}

- (void)removeEventsObject:(Event*)e
{
    [_events removeObject:e];
}

然后KVO会在调用这些通知时触发通知。