类具有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...]
的自定义访问器吗?
答案 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
答案 1 :(得分:9)
在这种情况下,我不会使用willChangeValueForKey
和didChangeValueForKey
。首先,它们意味着指示该路径上的值已经改变,而不是多对多关系中的值正在改变。如果您这样做,您可能希望使用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只存储指针,所以这不是一个开销,除非你使用大型数组和频繁的操作