观察NSMutableArray以进行插入/删除

时间:2008-11-19 15:54:17

标签: cocoa key-value-observing key-value-coding

类具有NSMutableArray类型的属性(和实例var),具有合成访问器(通过@property)。如果您使用以下方法观察此数组:

[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL];

然后在数组中插入一个对象:

[myObj.theArray addObject:NSString.string];

observeValueForKeyPath ...通知已发送。但是,以下内容确实发送了正确的通知:

[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string];

这是因为mutableArrayValueForKey返回一个代理对象,负责通知观察者。

但是,合成访问器不应该自动返回这样的代理对象吗?解决这个问题的正确方法是什么 - 我应该编写一个只调用[super mutableArrayValueForKey...]的自定义访问器吗?

7 个答案:

答案 0 :(得分:79)

  

但是,合成访问器不应该自动返回这样的代理对象吗?

没有

  

解决这个问题的正确方法是什么 - 我应该编写一个只调用[super mutableArrayValueForKey...]的自定义访问器吗?

没有。实施array accessors。当您拨打这些电话时,KVO会自动发布相应的通知。所以你要做的就是:

[myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]];

并且正确的事情将自动发生。

为方便起见,您可以编写addTheArrayObject:访问者。这个访问器将调用上述真正的数组访问器之一:

- (void) addTheArrayObject:(NSObject *) newObject {
    [self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]];
}

(您可以而且应该为数组中的对象填写适当的类,代替NSObject。)

然后,代替[myObject insertObject:…],你写[myObject addTheArrayObject:newObject]

可悲的是,add<Key>Object:及其对应的remove<Key>Object:是我检查过的,只能被KVO识别为set(如在NSSet中)属性,而不是数组属性,因此您无法获得免费的KVO通知除非你在它确实识别的访问器之上实现它们。我提交了一个关于此的错误:x-radar:// problem / 6407437

我的博客上有a list of all the accessor selector formats

答案 1 :(得分:9)

在这种情况下,我不会使用willChangeValueForKeydidChangeValueForKey。首先,它们意味着指示该路径上的值已经改变,而不是多对多关系中的值正在改变。如果您这样做,您可能希望使用willChange:valuesAtIndexes:forKey:。即便如此,使用像这样的手动KVO通知也是不好的封装。更好的方法是在实际拥有数组的类中定义方法addSomeObject:,其中包括手动KVO通知。这样,将对象添加到数组的外部方法也不需要担心处理数组所有者的KVO,这不会非常直观,如果您开始向对象添加对象,可能会导致不需要的代码和可能的错误来自几个地方的阵列。

在这个例子中,我实际上会继续使用mutableArrayValueForKey:。我对可变数组并不满意,但我相信通过阅读文档,这个方法实际上用一个新对象替换整个数组,所以如果性能是一个问题,你还需要实现insertObject:in<Key>AtIndex:和{{ 1}}在拥有数组的类中。

答案 2 :(得分:7)

当您只想观察更改的计数时,您可以使用聚合键路径:

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

但请注意,在阵列中的任何重新排序都不会触发。

答案 3 :(得分:3)

你对自己问题的回答几乎是正确的。不要在外部销售theArray。相反,声明一个不同的属性theMutableArray,对应于没有实例变量,并编写此访问器:

- (NSMutableArray*) theMutableArray {
    return [self mutableArrayValueForKey:@"theArray"];
}

结果是其他对象可以使用thisObject.theMutableArray对数组进行更改,这些更改会触发KVO。

其他答案指出,如果您同时实施insertObject:inTheArrayAtIndex:并且removeObjectFromTheArrayAtIndex:仍然正确,效率会提高。但是,其他对象无需了解这些内容或直接调用它们。

答案 4 :(得分:2)

如果你不需要setter,你也可以使用下面更简单的表格,它具有相似的性能(在我的测试中相同growth rate)和更少的样板。

// Interface
@property (nonatomic, strong, readonly) NSMutableArray *items;

// Implementation
@synthesize items = _items;

- (NSMutableArray *)items
{
    return [self mutableArrayValueForKey:@"items"];
}

// Somewhere else
[myObject.items insertObject:@"test"]; // Will result in KVO notifications for key "items"

这是有效的,因为如果未实现数组访问器并且没有该键的setter,mutableArrayValueForKey:将查找名为_<key><key>的实例变量。如果找到一个,代理会将所有消息转发给该对象。

请参阅these Apple docs,“有序集合的访问者搜索模式”部分,#3。

答案 5 :(得分:0)

您需要在addObject:willChangeValueForKey:来电中打包didChangeValueForKey:来电。据我所知,你修改的NSMutableArray无法知道观察其所有者的任何观察者。

答案 6 :(得分:0)

一种解决方案是使用NSArray并通过插入和删除从头开始创建,如

- (void)addSomeObject:(id)object {
    self.myArray = [self.myArray arrayByAddingObject:object];
}

- (void)removeSomeObject:(id)object {
    NSMutableArray * ma = [self.myArray mutableCopy];
    [ma removeObject:object];
    self.myArray = ma;
}

比获得KVO并且可以比较新旧阵列

注意:self.myArray不应该是nil,否则arrayByAddingObject:结果也是nil

根据具体情况,这可能是解决方案,因为NSArray只存储指针,所以这不是一个开销,除非你使用大型数组和频繁的操作