1

我得到错误

* 由于未捕获的异常“NSGenericException”而终止应用程序,原因:“* Collection <__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 以侦听消息,直到它们被卸载(在这种情况下它们会执行 a removeDelegate)。但是,在我看来,它在我对 UIScrollView 中的第二个视图执行 addDelegate 后直接死亡。

我怎样才能改进代码,以免发生这种情况?

更新
我想进一步澄清一下。发生的情况是视图控制器“StartPage”有一个带有页面控制器的 UIScrollView。它加载了几个其他视图(当前可见屏幕前 1 个)。每个视图都是一个实例 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];
    }
}
4

6 回答 6

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];
}
于 2011-11-02T00:56:12.583 回答
5

该错误表明,虽然某处的某些代码正在遍历您的列表,但您正在修改列表(这解释了调用 addDelegate 后的崩溃)。如果进行枚举的代码是修改列表的代码,那么您只需推迟修改直到枚举完成(例如,通过将它们收集到不同的列表中)。在不了解进行枚举的代码的情况下,不能说更多。

于 2011-10-27T21:12:46.017 回答
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
于 2011-11-07T11:58:03.130 回答
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 不是线程安全的,因此您应该始终从主线程调用您的方法。如果您没有明确添加任何额外线程,则无需担心。

于 2011-11-03T02:19:27.523 回答
0

关于Objective-C“快速枚举”的事情要永远记住。
“快速枚举”和 for 循环之间有两个很大的区别。

“快速枚举”比 for 循环更快。
但是
您不能修改您枚举的集合。

您可以在修改 NSSet 时询问您的 NSSet- (NSArray *)allObjects并枚举该数组。

于 2011-11-04T15:50:38.060 回答
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];
        }
    }
}
于 2011-11-05T07:49:14.933 回答