【问题标题】:Avoiding "NSArray was mutated while being enumerated"避免“NSArray 在被枚举时发生突变”
【发布时间】:2012-04-26 19:04:11
【问题描述】:

我有一个 NSMutableArray 存储用于 Box2d 物理模拟的鼠标关节。当使用多个手指弹奏时,我会收到异常声明

NSArray 在枚举时发生了变异

我知道这是因为我正在从数组中删除对象,同时还通过它进行枚举,从而使枚举无效。

我想知道的是,解决这个问题的最佳策略是什么?我在网上看到了一些解决方案:@synchronized,在枚举之前复制数组或将触摸关节放入垃圾数组以供以后删除(我不确定这是否可行,因为我需要从数组中删除鼠标关节将其从世界上移除后立即)。

【问题讨论】:

    标签: objective-c cocoa cocoa-touch collections nsmutablearray


    【解决方案1】:

    您始终可以在没有枚举器的情况下进行迭代。 这意味着一个常规的 for 循环,当您删除一个对象时:- 减少索引变量并 继续;。 如果您在进入 for 循环之前缓存了数组的计数,请确保在删除对象时也减少该计数。

    无论如何,我不明白为什么带有对象的数组会成为问题。我不知道您的确切情况和所涉及的技术,但理论上应该没有问题。 因为在大多数情况下,使用此方法时,您可以在第一次枚举中什么都不做,而在枚举删除数组时做真正的工作。 如果您遇到这样的情况,即在第一次枚举中,您 again 针对同一个数组检查某些内容,并且您需要知道这些对象不再存在,您可以添加一个检查以查看它们是否在删除数组中。

    无论如何,希望我有所帮助。 祝你好运!

    【讨论】:

    • "无论如何,希望我能帮上忙";当然帮助了我,很多!谢谢!
    • 这不适用于相同的问题,但适用于 NSMutableSet 而不是数组。您对这种情况有什么建议吗?
    • @VictorEngel 是的,使用您要删除的所有项目创建一个单独的集合,然后在原始集合(或它的 mutableCopy)上使用“minusSet”,并将第二个集合作为参数。 :)
    • 在 iOS6 中使用块枚举方法时出现此错误。
    【解决方案2】:

    你可以这样做:

    NSArray *tempArray = [yourArray copy];
    for(id obj in tempArray) {
        //It's safe to remove objects from yourArray here.
    }
    [tempArray release];
    

    【讨论】:

    • 这不是真的。在枚举异常时我仍然会发生变异。
    • 是否应该像 NSArray *tempArray = [NSArray arrayWithArray:yourArray] 一样初始化 tempArray?看起来这个 sn-p 的代码不会做 OP 的意图
    • @TimArnold 我很确定这会给出相同的结果。
    • @Brendt 请出示您的代码 sn-p。这个答案对我来说似乎是正确的。因为我们使用不同的容器,它存储了 yourArray 中保留的所有对象。
    • 使用for (int = 0; i < yourArray.count; i++) { /* It's safe to remove objects from yourArray here.*/ } ,这不会使用数组的枚举数。
    【解决方案3】:

    最简单的方法是通过数组向后枚举,这意味着删除对象时不会影响下一个索引。

    for (NSObject *object in [myMutableArray reverseObjectEnumerator]) {
        // it is safe to test and remove the current object      
        if (AddTestHere) {
            [myMutableArray removeObject: object];
        }
    }
    

    【讨论】:

    • 最简单高效的答案
    • 这是不正确的。来自the reverseObjectEnumerator documentation:“当你将此方法与 NSArray 的可变子类一起使用时,你不能在枚举期间修改数组。”
    • 这是一个一般性的警告,但显然它比这更微妙。通过插入或删除比当前索引更早的元素来修改 NSMutableArray 会产生问题,但这不是这里要做的。相信我,我已经使用这种技术 6 年多了,没有任何问题。它工作得很好。
    • 仅仅因为它“工作正常”并不意味着它是正确的。您可以调用私有 API,它也将“正常工作”,直到 Apple 决定更改它,然后您将崩溃。您依赖于内部实现细节并明确执行文档告诉您不要执行的操作。请使用一种受支持的方式来执行此操作。
    • 别傻了。我依赖什么“内部实现细节”?
    【解决方案4】:

    锁定(@synchronized)操作比一遍又一遍地复制整个数组要快得多。当然,这取决于数组有多少元素,以及它执行的频率。 假设您有 10 个线程同时执行此方法:

    - (void)Callback
    {
      [m_mutableArray addObject:[NSNumber numberWithInt:3]];
      //m_mutableArray is instance of NSMutableArray declared somewhere else
    
      NSArray* tmpArray = [m_mutableArray copy];
      NSInteger sum = 0;
      for (NSNumber* num in tmpArray)
          sum += [num intValue];
    
      //Do whatever with sum
    }
    

    每次都复制 n+1 个对象。你可以在这里使用锁,但是如果有 100k 元素要迭代怎么办?数组将被锁定直到迭代完成,其他线程将不得不等待直到锁定被释放。我认为在这里复制对象更有效,但这也取决于该对象有多大以及您在迭代中做了什么。锁应始终保持最短时间。所以我会用 lock 来做这样的事情。

    - (void)Callback
    {
      NSInteger sum = 0;
      @synchronized(self)
      {
        if(m_mutableArray.count == 5)
          [m_mutableArray removeObjectAtIndex:4];
        [m_mutableArray insertObject:[NSNumber numberWithInt:3] atIndex:0];
    
        for (NSNumber* num in tmpArray)
          sum += [num intValue];
      }
      //Do whatever with sum
    }
    

    【讨论】:

      【解决方案5】:

      以上所有对我都不起作用,但这样做:

       while ([myArray count] > 0) 
       {
            [<your delete method call>:[myArray objectAtIndex:0]];
       }
      

      注意:这会将其全部删除。如果您需要选择需要删除的项目,那么这将不起作用。

      【讨论】:

      • 清空数组不需要一个一个删除对象。你可以说 myArray = [NSMutableArray init];变异不仅仅是删除,它还​​包括修改对象,这通常是你想要做的。在任何一种情况下,“枚举和变异”数组的正确方法是首先创建数组的副本,并使用该副本枚举对象,同时按预期对原始数组进行变异。
      【解决方案6】:

      使用 for 循环而不是枚举是可以的。但是一旦你开始删除数组元素,在使用线程时要小心。仅仅减少计数器是不够的,因为您可能正在删除错误的东西。 正确的方法之一是创建数组的副本,迭代副本并从原始副本中删除。

      edc1591 是正确的方法。

      [Brendt:在遍历副本时需要从原始中删除]

      【讨论】:

      • 感谢在迭代副本时从原始文件中删除的提示,我不清楚。
      【解决方案7】:
      for(MyObject *obj in objQueue)
      {
      //[objQueue removeObject:someof];//NEVER removeObject in a for-in loop
          [self dosomeopearitions];
      }
      
      //instead of remove outside of the loop
      [objQueue removeAllObjects];
      

      【讨论】:

        【解决方案8】:

        我遇到了同样的问题,解决方案是枚举副本并改变原件,正如 edc1591 正确提到的那样。我已经尝试了代码,但我不再收到错误消息。如果您仍然收到错误,则意味着您仍在 for 循环内更改副本(而不是原始副本)。

        NSArray *copyArray = [[NSArray alloc] initWithArray:originalArray];
        
        for (id obj in copyArray) { 
            // tweak obj as you will 
            [originalArray setObject:obj forKey:@"kWhateverKey"];
        }
        

        【讨论】:

        • setObject:forKey 是一种字典,而不是数组的方法(另外,它必须是可变版本)。否则,方法正确。
        【解决方案9】:

        也许您在枚举 mutableAray 时删除或添加了对象。 在我的情况下,这种情况下出现了错误。

        【讨论】:

          【解决方案10】:

          我遇到了这个错误,在我的例子中是因为多线程的情况。一个线程正在枚举一个数组,而另一个线程同时从同一个数组中删除了对象。希望这对某人有所帮助。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-09-21
            • 2012-05-04
            • 1970-01-01
            • 2012-07-30
            • 2011-10-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多