English 中文(简体)
观察NSMutableArray的插入/删除。
原标题:
  • 时间:2008-11-19 15:54:17
  •  标签:

一個類具有類型為NSMutableArray的屬性(和實例變量),使用經過合成的訪問器(通過 @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 ...]

最佳回答

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

不。

如何正确地解决这个问题 - 我应该编写一个自定义访问器,只是调用 [super mutableArrayValueForKey ...]

不。 Implement the array accessors. When you call these, KVO will post the appropriate notifications automatically. So all you have to do is:

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

正确的事情会自动发生。

为了方便起见,您可以编写一个名为addTheArrayObject:的访问器。该访问器将调用上述实际数组访问器之一:

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

你可以和应该在数组中填写正确的对象类,而不是NSObject

然后,你可以写成 [myObject addTheArrayObject:newObject] 而不是 [myObject insertObject:...]

遗憾的是,add<Key>Object:和它的对应项remove<Key>Object:在我上次检查时仅被KVO识别为set(如NSSet)属性,而不是数组属性,因此使用它们不会获得免费的KVO通知,除非您在它已经认可的存取器之上实现它们。我已经报告了一个错误:x-radar://problem/6407437

我在我的博客上有所有访问器选择器格式的列表

问题回答

在这种情况下,我不会使用willChangeValueForKeydidChangeValueForKey。首先,它们旨在指示该路径上的值已更改,而不是正在更改的关系的值。如果您以这种方式做,您需要使用willChange:valuesAtIndexes:forKey: 替代。即便如此,像这样使用手动的KVO通知会损害封装性。更好的方式是在实际拥有该数组的类中定义一个addSomeObject:方法,该方法将包括手动的KVO通知。这样,向数组添加对象的外部方法就不需要担心也要处理数组拥有者的KVO,这样做不会非常直观,并且可能会导致不必要的代码和可能的错误,如果您从几个地方向该数组添加对象。

在这个例子中,我实际上会继续使用 mutableArrayValueForKey:。我对可变数组不确定,但我相信从阅读文档中,这个方法实际上是用新对象替换整个数组,所以如果性能是一个问题,你也会想要在拥有数组的类中实现insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:

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

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

但要注意,数组的任何重排都不会触发。

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

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

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

其他的回答指出,如果你也实现insertObject:inTheArrayAtIndex:removeObjectFromTheArrayAtIndex:,效率会得到提高,这一点仍然是正确的。但是,并没有必要让其他对象知道这些方法或直接调用它们。

如果您不需要setter,也可以使用以下更简单的形式,它具有类似的性能(在我的测试中具有相同的增长率)且更少的模板代码。

// 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>的实例变量。如果找到了一个,代理将把所有消息转发到这个对象。

请查看 这些苹果文档,第三节“有序集合的访问器搜索模式”。

你需要在你的addObject:调用中使用willChangeValueForKey:didChangeValueForKey:调用。据我所知,NSMutableArray无法了解任何观察其所有者的观察者。

一个解决方法是使用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仅存储指针,除非您使用大型数组和频繁操作,否则这不会产生太多负担。





相关问题