NSMutableDictionary KVO

6

我尝试使用KVO观察字典中的更改。

示例:

dictionary = [NSMutableDictionary new];
[dictionary setObject:@"test1" forKey:@"key1"];
[dictionary setObject:@"test2" forKey:@"key2"];    
[dictionary setObject:@"test3" forKey:@"key1"];

我希望您能够在字典添加、删除或替换(即在上述情况中,无论何时调用setObject方法)时,挂钩观察者。
因此,总之: 我需要一个函数。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

当我向字典中添加任何新条目、删除任何条目或替换任何条目时,会调用此函数。

注意:我不想指定要观察哪些键(例如仅在添加 @"key1" 时观察),因为这种解决方案不具有可扩展性。


如果您在字典上观察到键@"@count",会发生什么? - Lily Ballard
不行。如果我用@"key2"替换@"@count",它可以工作,但这不是通用解决方案。 - John Twigg
5个回答

8

继承NSMutableDictionary有点烦人,因为NSDictionary及其相关类是类族(class clusters)。虽然这是可行的,如果您必须将对象本身传递给另一组类,则可能需要这样做。否则,创建一个具有相同基本API并在内部使用NSMutableDictionary对象进行存储的组合类可能更容易。CocoaWithLove.com上有一篇相当不错的文章,介绍了如何实现Ordered Dictionary Subclassing

但是,这并不能完全解决您的问题。我建议您从上面提到的子类或装饰器类开始,然后显式添加支持-(NSArray*)allKeys,这是NSDictionary本身的标准访问器。然后,您可以添加支持传递所有键(allKeys)的更改消息,从而使其成为可观察的。

这可以通过在-setObject:forKey:-removeObjectForKey:方法周围添加以下代码来完成。

- (void)setObject:(id)anObject forKey:(id)aKey
{
    BOOL addKey=NO;
    if (![dictionary objectForKey: aKey]) {
        addKey=YES;
        [self willChangeValueForKey: @"allKeys"];   
    }
    [dictionary setObject:anObject forKey:aKey];
    if (addKey)
        [self didChangeValueForKey: @"allKeys"];
}

- (void)removeObjectForKey:(id)aKey
{
    [self willChangeValueForKey: @"allKeys"];
    [dictionary removeObjectForKey:aKey];
    [self didChangeValueForKey: @"allKeys"];
}

在这里所做的是,当字典的键改变以标记数组中的变化时,我们向类添加显式的KVO通知。
这将处理添加和删除。如果您希望在相同的基础上通知更改,可以删除if语句,并使所有键在设置或删除时通知,如下所示:
- (void)setObject:(id)anObject forKey:(id)aKey
{
    [self willChangeValueForKey: @"allKeys"];   
    [dictionary setObject:anObject forKey:aKey];
    [self didChangeValueForKey: @"allKeys"];
}

然后,在您的代码中,您为此对象的键“allKeys”放置了一个单个观察者,您将在每次更改项目时接收通知。


我同意这个结论。谢谢gaige。我将要实现它。 - John Twigg
如果它有效,请不要忘记为您发现有用的答案投票,并标记提供解决方案的答案。 - gaige
当NSMutableDictionary增长到一定大小时,这些方法不会再为每个键调用吗? - Pétur Ingi Egilsson
“增大”是一个有趣的描述选择。我的代码中展示了两种变体,一种用于记录字典中键的添加和删除,另一种则显示对字典进行更改的任何时间(基本上是在“setObject:forKey:”方法中删除“if”语句)。 - gaige

1
我通过向可变字典“translator”添加观察者来解决类似的问题,具体做法如下:

[self addObserver:self forKeyPath:@"translator.@count" options:0 context:NULL];

我的应用以经典方式管理数据,使用表视图、控制器和字典作为KVO属性“translator”。该字典绑定到XIB中的NSDictionaryController上,表视图内容绑定到控制器上。
以下是表视图的连接:

enter image description here

现在,在下列任何情况下,我都能捕捉到更改:

  • 添加键值对
  • 删除键值对
  • 更改键
  • 更改值

备注:不幸的是,这种方法无法用于NSMutableArrays。更改不会被识别。


0

你不能子类化NSMutableDictionary并覆盖各种setter方法吗?例如,通过调用super来重写setObject:forKey:,然后立即调用addObserver...

你也可以编写一个NSMutableDictionary的包装器,在其中强制自己使用自定义setter方法来操作底层的NSMutableDictionary。

也许我需要更多上下文了解你的限制或可扩展性意图。


覆盖NSMutableDictionary是非法的(让我很惊讶)。调用[super SetObject: forKey:]会导致异常,说某个抽象类。我倾向于使用组合并重新实现字典可以提供的所有函数(非常麻烦且不多态)。可扩展性问题是指您只能观察字典中“key1”的更改,而不能观察对字典的“*”更改。因此,如果向字典中添加了新键,我无法通知观察者。 - John Twigg
确实很有趣。到目前为止,我认为装饰器类可能是最好的选择。你不必重新实现所有方法。你只需要将字典作为成员,并对任何非KVO操作调用[myObject.dictionary nsdictionarymethod]即可。然后重新实现添加、设置和删除方法即可。 - Vinnie

0
我认为另一种做法是使用下面的覆盖方法,以防您正在观察取决于recentBrazilReals、recentEuEuro、recentUkPounds和recentJapanYen的值的NSMutableDictionary "allRecentCurrencyData",观察者会被调用,但缺点是需要事先知道键的名称才能实现这个。
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"allRecentCurrencyData"]) {
       NSArray *affectingKeys = @[@"recentBrazilReals", @"recentEuEuro",@"recentUkPounds",@"recentJapanYen"];
      keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

0

我希望这会有所帮助

- (void)addObserver:(id)observer {
    for (id key in grid)
        [self addObserver:observer
               forKeyPath:[key description]
                  options:0
                  context:key];
}

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