76

一个类具有类型为 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...]吗?

4

7 回答 7

79

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

不。

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

不。实现数组访问器。当您调用这些时,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 识别为集合(如在 NSSet 中)属性,而不是数组属性,所以你不会得到免费的 KVO 通知,除非你在它确实识别的访问器之上实现它们. 我为此提交了一个错误:x-radar://problem/6407437

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

于 2008-11-20T02:10:56.263 回答
9

在这种情况下,我不会使用willChangeValueForKeyand 。didChangeValueForKey一方面,它们旨在表明该路径上的值已更改,而不是一对多关系中的值正在更改。如果你这样做,你会想要使用willChange:valuesAtIndexes:forKey:它。即便如此,使用像这样的手动 KVO 通知是不好的封装。更好的方法是在实际拥有数组的类中定义一个方法addSomeObject:,其中包括手动 KVO 通知。这样,将对象添加到数组的外部方法也不需要担心处理数组所有者的 KVO,这不是很直观,如果您开始向数组添加对象,可能会导致不需要的代码和可能的错误从几个地方排列。

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

于 2008-11-19T19:51:49.853 回答
7

当您只想观察计数发生变化时,可以使用聚合键路径:

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

但请注意,数组中的任何重新排序都不会触发。

于 2013-10-31T14:07:13.663 回答
3

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

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

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

其他答案指出,如果您也实施insertObject:inTheArrayAtIndex:并且removeObjectFromTheArrayAtIndex:仍然正确,效率会提高。但是没有必要让其他对象知道这些或直接调用它们。

于 2010-06-17T04:02:28.597 回答
2

如果您不需要 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"

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

请参阅这些 Apple 文档,“有序集合的访问器搜索模式”部分,#3。

于 2014-04-17T01:29:18.250 回答
0

你需要把你的addObject:电话willChangeValueForKey:didChangeValueForKey:电话包装起来。据我所知,您正在修改的 NSMutableArray 无法了解任何观察其所有者的观察者。

于 2008-11-19T17:49:41.813 回答
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 仅存储指针,因此这不是太大的开销,除非您使用大型数组和频繁的操作

于 2013-10-11T08:35:28.257 回答