我收到错误
*由于未捕获的异常'NSGenericException'而终止应用程序,原因:'* 集合< __ NSCFSet:0x6b66390>在被列举时被突变。'
向我的班级添加新委托时。或者至少,这就是我认为问题所在。
这是我的代码:MyAppAPI.m
[...]
static NSMutableSet *_delegates = nil;
@implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSMutableSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
[_delegates addObject:delegate];
}
+ (void)removeDelegate:(id)delegate
{
[_delegates removeObject:delegate];
}
[...]
@end
MyAppAPI是一个单例,我可以在整个应用程序中使用它。无论我在哪里(或应该能够)做到:[MyAppAPI addDelegate:self]
这很有效,但仅限于第一个视图。该视图有一个带有PageViewController的UIScrollView,它可以在其自身中加载新视图。这些新视图注册到MyAppAPI以收听消息,直到它们被卸载(在这种情况下,它们执行removeDelegate
)。
但是,在我看来,它在UIScrollView的第二个视图上执行了addDelegate后直接死了。
我怎样才能改进代码,以免发生这种情况?
更新
我想进一步澄清一下。
会发生什么是视图控制器“StartPage”具有带页面控制器的UIScrollView。它加载了几个其他视图(在当前可见屏幕之前1个)。
每个视图都是一个instans PageViewController,它使用上面显示的addDelegate函数将自身注册到名为MyAppAPI的全局单例。
但是,据我所知,当viewcontroller 2注册自己时,viewcontroller 1仍在从委托中读取,因此错误显示在上面。
我希望我能清楚地表明这一点。我尝试过一些东西,但没有任何帮助。 我需要使用addDelegate向代理注册,即使在从代理中读取时也是如此。我该怎么做?
更新2 这是一种反应方法:
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
答案 0 :(得分:11)
所以这是你可能正在做的一个例子。
+ (void)iteratingToRemove:(NSArray*)items {
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[MyAppAPI removeDelegate:delegate]; //error you are editing an NSSet while enumerating
}
}
}
以下是你应该如何正确处理这个问题:
+ (void)iteratingToRemove:(NSArray*)items
{
NSMutableArray *delegatesToRemove = [[NSMutableArray alloc] init];
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[delegatesToRemove addObject:delegate];
}
}
for(id delegate in delegatesToRemove) {
[MyAppAPI removeDelegate:delegate]; //This works better
}
[delegatesToRemove release];
}
答案 1 :(得分:5)
错误表明,虽然某些代码在某个位置正在浏览列表,但您正在修改列表(这解释了调用addDelegate后的崩溃)。如果执行枚举的代码是修改列表的代码,那么您只需要推迟修改,直到枚举完成(例如,通过在不同的列表中收集它们)。在不知道任何关于进行枚举的代码的情况下,不能说更多。
答案 2 :(得分:2)
一个简单的解决方案,不要使用可变集。由于各种原因,包括这个原因,它们很危险。
您可以使用-copy和-mutableCopy在NSSet(和许多其他类)的可变和非可变版本之间进行转换。注意所有复制方法返回一个保留计数为1的新对象(就像alloc一样),所以你需要释放它们。
除了具有较少的错误可能性之外,非可变对象使用起来更快并且使用更少的内存。
[...]
static NSSet *_delegates = nil;
@implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable addObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
+ (void)removeDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable removeObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
[...]
@end
答案 3 :(得分:1)
Scott Hunter是对的 - 当你枚举集合的项目时修改NSSet是个问题。您应该有一个应用程序崩溃的堆栈跟踪。它可能有一行您要在_delegates集中添加/删除。这是 您需要进行修改。这很容易做到。不要添加/删除集合,而是执行以下操作:
NSMutableSet *tempSet = [_delegates copy];
for (id delegate in _delegates)
{
//add or remove from tempSet instead
}
[_delegates release], _delegates = tempSet;
此外,NSMutableSet is not thread safe,所以你应该总是从主线程调用你的方法。如果您没有明确添加任何额外的线程,则无需担心。
答案 4 :(得分:0)
永远记住Objective-C“快速枚举”的事情。
“快速枚举”和for循环之间有两个很大的区别。
“快速枚举”比for循环更快
但
您无法修改枚举的集合。
您可以向NSSet询问- (NSArray *)allObjects
并在修改NSSet时枚举该数组。
答案 5 :(得分:0)
当一个线程试图修改(添加,删除)数组而其他线程正在迭代它时,会出现此错误。
使用NSLock或同步方法解决此问题的一种方法。添加,删除和迭代方法的方法无法并行调用。 但这会对性能和/或响应性产生影响,因为任何添加/删除都必须等待迭代在阵列上的线程。
受益于Java的CopyOnWriteArrayList的更好的解决方案是创建数组的副本并迭代副本。因此,代码中唯一的变化是: -
//better solution
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
NSArray *copyOfDelegates = [_delegates copy]
for (id delegate in copyOfDelegates)
{
if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
使用具有性能影响的锁的解决方案
//not a good solution
+ (void)addDelegate:(id)delegate
{
@synchronized(self){
[_delegates addObject:delegate];
}
}
+ (void)removeDelegate:(id)delegate
{
@synchronized(self){
[_delegates removeObject:delegate];
}
}
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
@synchronized(self){
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
}