NSArrayController and KVO

5
当调用更新基础数组的方法时,我需要做什么来更新绑定到NSArrayController的tableView呢?这个例子可能会澄清一些问题。
当我的应用程序启动时,它会创建一个SubwayTrain。当SubwayTrain被初始化时,它会创建一个单独的SubwayCar。SubwayCar有一个可变数组'passengers'。当一个Subway car被初始化时,乘客数组被创建,并放入一些People对象(比如一个名为"ticket collector"和另一个名为"homeless guy"的人)。这些家伙总是在SubwayCar上,所以我在初始化时创建它们并将它们添加到乘客数组中。
在应用程序的生命周期中,人们会上车。在SubwayCar上调用'addPassenger',并将人作为参数传递进去。
我有一个绑定到subwayTrain.subwayCar.passengers的NSArrayController,在启动时我的售票员和无家可归的人都很好地显示出来了。但是当我使用[subwayCar addPassenger:]时,tableView没有更新。我已经确认乘客肯定被添加到了数组中,但在GUI中没有更新任何内容。
我可能做错了什么?我的直觉是与KVO有关——即array controller不知道在调用addPassenger时更新(即使addPassenger调用[passengers addObject:])。我在这里可能错了什么——如果有帮助,我可以发布代码。
感谢任何愿意帮忙的人。
更新
所以,事实证明我可以通过将addPassenger方法从原来的形式更改为:
[seatedPlayers addObject:person];

to

NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];

[newSeatedPlayers addObject:sp];

[seatedPlayers release];

[self setSeatedPlayers:newSeatedPlayers];

我猜这是因为我使用了[self setSeatedPlayers]。这样做正确吗?这似乎非常麻烦,需要复制数组,释放旧数组,并更新副本(而不是仅添加到现有数组中)。


表格视图如何绑定到控制器?您是否将每个表格列绑定到 subwayTrain.subwayCar.passengers 属性的一个属性(例如,名称列绑定到 subwayTrain.subwayCar.passengers.name)? - outis
是的,完全正确。当启动时,乘客姓名会显示出来。 - Ben Packard
5个回答

7

我不确定这是否被认为是一个 bug,但是 addObject: (以及 removeObject:atIndex:)不会生成 KVO 通知,这就是为什么数组控制器/表视图没有得到更新的原因。为了符合 KVO 标准,请使用 mutableArrayValueForKey:

示例:

[[self mutableArrayValueForKey:@"seatedPlayers"] addObject:person];

你还需要实现insertObject:inSeatedPlayersAtIndex:,因为默认的KVO方法非常缓慢(它们创建一个全新的数组,将对象添加到该数组中,并将原始数组设置为新的数组--非常低效)

- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
   [seatedPlayers insertObject:object atIndex:index];
}

注意,当数组控制器添加对象时,此方法也会被调用,因此它还是一个很好的钩子,可以用于注册撤销操作等事项。

2
我没有尝试过这个方法,所以不能确定它是否有效,但是通过在ArrayController上调用insertObject:atArrangedObjectIndex:不会收到KVO通知吗?

是的,使用NSArrayController来改变数组会使NSArrayController知道这种变化,因此它(以及绑定到它的事物)应相应地更新。 - ipmcc

1

So, it turns out I can get this to work by changing by addPassenger method from

[seatedPlayers addObject:person];

to

NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];

I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?

首先,它是带有冒号的setSeatedPlayers:。在Objective-C中这非常重要。
使用自己的setter是正确的方法,但你正在使用不正确的正确方法。虽然它可以工作,但你仍然比需要编写更多的代码。
你应该实现设置访问器,例如addSeatedPlayersObject:。然后,发送给自己这条消息。这使得添加人员变成一个短一行代码。
[self addSeatedPlayersObject:person];

只要您遵循符合KVC标准的访问器格式, 您将免费获得KVO通知,就像使用setSeatedPlayers:一样。

相比于setSeatedPlayers:,这种方法的优点是:

  • 更短的代码来改变集合。
  • 因为它更短,所以更简洁。
  • 使用特定的集合修改访问器提供了特定的集合修改KVO通知,而不是整个集合都发生变化的通知。

我也更喜欢这种解决方案,而不是使用mutableSetValueForKey:,因为它更简洁,而且在那个字符串字面量中很容易拼错键名。(Uli Kusterer有一个宏可以在发生这种情况时引发警告, 当您确实需要与KVC或KVO本身交互时,这非常有用。)


1
键值观察魔法的关键在于键值兼容性。你最初使用的方法名字是addObject:,它只与“无序访问模式”相关联,而你的属性是一个索引属性(NSMutableArray)。当你将属性更改为无序属性(NSMutableSet)时,它就可以正常工作了。考虑NSArray或NSMutableArray作为索引属性、NSSet或NSMutableSet作为无序属性。你真的必须仔细阅读这个章节才能知道如何使魔法发生... 键值兼容性。即使你不打算使用它们,不同类别的一些“必需”的方法也是存在的。

0
  1. 当更改不会导致 KVO 通知时,使用 willChangeValueForKey:didChangeValueForKey: 包装成员变量的更改。这在直接更改实例变量时非常方便。

  2. 当更改不会导致 KVO 通知时,使用 willChangeValueForKey:withSetMutation:usingObjects:didChangeValueForKey:withSetMutation:usingObjects: 包装集合内容的更改。

  3. 使用 [seatedPlayers setByAddingObject:sp] 来缩短代码并避免不必要的可变集合分配。

总的来说,我会采用以下方式:

[self willChangeValueForKey:@"seatedPlayers"
            withSetMutation:NSKeyValueUnionSetMutation 
               usingObjects:sp];
[seatedPlayers addObject:sp];
[self didChangeValueForKey:@"seatedPlayers" 
           withSetMutation:NSKeyValueUnionSetMutation 
              usingObjects:sp];

或者这样:

[self setSeatedPlayers:[seatedPlayers setByAddingObject:sp]];

使用后一种选择会自动调用列在1下的函数,而第一种选择应该具有更好的性能。


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