使用NSMutableSet时出错

时间:2011-10-27 21:03:31

标签: iphone objective-c ios xcode enumerate

我收到错误

  

*由于未捕获的异常'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];
    }
}

6 个答案:

答案 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];
        }
    }
}