Cocoa 观察NSMutableArray以进行插入/删除
一个类有一个NSMutableArray类型的属性(和实例变量),带有合成的访问器(通过Cocoa 观察NSMutableArray以进行插入/删除,cocoa,key-value-observing,key-value-coding,Cocoa,Key Value Observing,Key Value Coding,一个类有一个NSMutableArray类型的属性(和实例变量),带有合成的访问器(通过@property)。如果使用以下方法观察此阵列: [myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL]; 然后在数组中插入一个对象,如下所示: [myObj.theArray addObject:NSString.string]; 一条有观察价值的分叉道。。。通知未发送。但是,以下情况确实会发送正确的通知: [[my
@property
)。如果使用以下方法观察此阵列:
[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL];
然后在数组中插入一个对象,如下所示:
[myObj.theArray addObject:NSString.string];
一条有观察价值的分叉道。。。通知未发送。但是,以下情况确实会发送正确的通知:
[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string];
这是因为mutableArrayValueForKey
返回负责通知观察者的代理对象
但是合成的访问器不应该自动返回这样一个代理对象吗?解决这个问题的正确方法是什么?我是否应该编写一个只调用
[super-mutableArrayValueForKey…]
的自定义访问器?您需要包装addObject:
调用willChangeValueForKey:
和didChangeValueForKey:
调用。据我所知,您正在修改的NSMutableArray无法了解任何观察者在监视其所有者。在这种情况下,我不会使用willChangeValueForKey
和didChangeValueForKey
。首先,它们是用来表示路径上的值已经改变,而不是多对多关系中的值正在改变。如果这样做的话,您可能希望使用willChange:valuesAtIndexes:forKey:
。即使如此,像这样使用手动KVO通知也是不好的封装。更好的方法是在实际拥有数组的类中定义一个方法addSomeObject:
,该方法将包括手动KVO通知。这样,将对象添加到阵列中的外部方法也不需要担心如何处理阵列所有者的KVO,这不是很直观,如果从多个位置开始将对象添加到阵列中,可能会导致不必要的代码和错误
在本例中,我实际上将继续使用mutableArrayValueForKey:
。我对可变数组不是很肯定,但通过阅读文档,我相信该方法实际上用一个新对象替换了整个数组,因此如果性能是一个问题,那么您还需要在拥有该数组的类中实现insertObject:inandex:
和removeObjectFromAtIndex:
但是合成的访问器不应该自动返回这样一个代理对象吗
没有
解决这个问题的正确方法是什么——我应该编写一个只调用[super-mutableArrayValueForKey…]
的自定义访问器吗
不,执行这个命令。当您调用这些时,KVO将自动发布相应的通知。所以你所要做的就是:
[myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]];
正确的事情会自动发生
为了方便起见,您可以编写一个addTheArrayObject:
accessor。此访问器将调用上述实际阵列访问器之一:
- (void) addTheArrayObject:(NSObject *) newObject {
[self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]];
}
(您可以而且应该为数组中的对象填写适当的类,而不是NSObject
)
然后,您将编写[myObject addthearrayoobject:newObject]
,而不是[myObject insertObject:…]
遗憾的是,addObject:
及其对应的removeObject:
在我最后一次检查时,KVO只能识别set(如NSSet)属性,而不能识别数组属性,因此除非在它能够识别的访问器之上实现它们,否则您无法获得免费的KVO通知。我提出了一个错误:x-radar://problem/6407437
我在我的博客上找到了答案。你自己对自己问题的回答几乎是正确的。不要向外部出售阵列。相反,请声明一个不同的属性,theMutableArray
,该属性对应于无实例变量,并编写此访问器:
- (NSMutableArray*) theMutableArray {
return [self mutableArrayValueForKey:@"theArray"];
}
结果是其他对象可以使用此对象。mutablearray
对数组进行更改,这些更改将触发KVO
其他答案指出,如果同时在ArrayatIndex:
中实现insertObject:InArrayatIndex:
和从ArrayatIndex:
中删除对象,则效率会提高,这些答案仍然正确。但是其他对象不需要知道这些或直接调用它们。一种解决方案是使用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只存储指针,这不会带来太大的开销,除非您使用大型数组和频繁的操作,否则当您只想观察计数的变化时,可以使用聚合密钥路径:
[myObj addObserver:self forKeyPath:@"theArray.@count" options:0 context:NULL];
但是请注意,阵列中的任何重新排序都不会触发。如果您不需要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:
将查找名为\uu
或
的实例变量。如果找到一个,代理将把所有消息转发到此对象
请参见第3节“有序集合的访问者搜索模式”。1问题是,当您添加观察者时,您正在观察某个对象的属性。数组是该属性的值,而不是属性本身。这就是为什么您需要使用访问器或-mutableArrayValueForKey:来修改数组。您的最后一点似乎已经过时了-如果我实现了添加和删除访问器,我会收到有关NSArray属性的免费KVO通知。使用此快捷方式时,我只能观察到关键路径“theArray”上的更改,而不是“theMutableArray”上的更改除此之外,所有功能都非常完美,我可以操作mutableArray,就像它是一个
NSMutableArray
属性一样