【问题标题】:How to safely loop over a mutable array while modifying it?如何在修改可变数组时安全地循环它?
【发布时间】:2012-09-01 19:21:10
【问题描述】:

我有一个controller,它有一个数组保存actors。 Actor 是一个对象,将被 controller 调用。

问题:controller 遍历actors 数组并向每个参与者发送-actionMessage。 Actor 可以使用 controller 创建和注册另一个 Actor,或者从控制器的 Actor 数组中删除一个 Actor 甚至它自己。它通过两种方法路由:

-registerActor:(Actor*)actor;
-unregisterActor:(Actor*)actor;

因此,当控制器迭代演员数组时,演员列表可以改变。 编辑:任何新添加的演员也必须经过循环。

解决此问题的最佳做法是什么?我应该在迭代之前创建一个actors数组的副本吗?

【问题讨论】:

    标签: iphone objective-c ios arrays


    【解决方案1】:

    创建一个可变数组的副本并对其进行迭代。

    NSArray *loopArray = [NSArray arrayWithArray: yourActorArray];
    

    或者

    NSArray *loopArray = [yourActorArray copy];
    //in this case remember to release in nonARC environment
    

    【讨论】:

    • 就我而言,这是唯一真正理智的答案。替代方案大多是 hacks(有些人完全忽略了你可以使用NS[Mutable]IndexSet 来删除循环后的项目,如果这是唯一发生的修改)。这不是特别愉快,但这是处理这种情况的最安全的方法。
    • 我更新了问题以反映这一点:添加 Actor 时,循环也必须对其进行处理。使用副本我不会处理新添加的演员。那么 Set 是更好的选择,可以从变异数组中区分出不在处理集中的所有参与者,并让循环也遍历这些参与者?
    【解决方案2】:

    这是我通常做的……

    NSMutableArray *discardedItems = [NSMutableArray array];
    SomeObjectClass *item;
    
    for (item in originalArrayOfItems) {
        if ([item shouldBeDiscarded])
            [discardedItems addObject:item];
    }
    
    [originalArrayOfItems removeObjectsInArray:discardedItems];
    

    希望这会有所帮助。

    【讨论】:

    • 看起来很聪明,但它不能处理在迭代过程中向数组中添加额外元素的情况,并且这些元素中的每一个都必须经过该循环。
    【解决方案3】:

    枚举一个可变数组的标准过程需要在您逐步完成时对其进行更改,即制作一个副本并对其进行迭代,从而更改原始数组。我猜演员的 NSMutableArray 是属于您的控制器的属性,并且 registerActor: 和 unregisterActor: 都改变了这个数组。如果您单步执行副本,则可以通过方法从原始属性中删除演员,而无需更改副本。

    NSMutableArray *actorArrayCopy = [self.actorArray copy];
    for (id object in actorArrayCopy){
        //do stuff
    }        
    

    在某些情况下,您可以放弃快速枚举并使用标准的 for 循环,但是,这样做是有风险的。如果在数组中插入或删除对象,则索引将发生变化(AFAIK),这意味着您最终可能会跳过元素或多次遍历一个元素。

    有些人将要更改(例如删除)的元素存储在快速枚举中的单独数组中,并在枚举完成后立即执行所有更改,但是您使用单独的方法来添加和删除元素;元素本身决定应该发生什么并通知您。这会使事情变得更复杂,所以副本可能效果最好。

    【讨论】:

      【解决方案4】:

      您可以通过这样做来避免复制数组:

      for(int i=0; i<[array count]; i++)
      {
          if(condition)
          {
              [array removeObjectAtIndex:i];
      
              i --;
              continue;
          }
      }
      

      【讨论】:

        【解决方案5】:

        您应该使用集合而不是数组。然后你可以复制set,操作完成后,拿diff看看有什么变化。

        【讨论】:

          【解决方案6】:

          我制作了一个名为 smartFor 的宏,它允许您进行与 for 循环快速枚举相同的设置,但可以处理数组的修改。它利用了一些 C 和 CLANG 漏洞,非常简单干净。


          宏:

          ///Allows for modifying an array during virtual fast enumeration
          #define smartFor(__objectDeclaration, __array, __block) {\
          int __i = 0;\
          while (__i < __array.count) {\
          __objectDeclaration = __array[__i];\
          NSObject *__object = __array[__i];\
          [__block invoke];\
          if ([__array indexOfObjectIdenticalTo:__object] != NSNotFound && ((int)[__array indexOfObjectIdenticalTo:__object]) <= __i) {\
          __i = ((int)[__array indexOfObjectIdenticalTo:__object])+1;\
          }\
          }\
          }
          

          它会自动处理您可能会做的所有 [基本] 不稳定的事情,例如添加对象(在数组中的任何位置,在“快速枚举”[1] 期间的任何时间点)、删除对象、调整对象索引、修改对象等


          示例用法:

          smartFor(NSNumber *aNumber, myArray, ^{
              if ([aNumber isEqualToNumber:@(3)]) {
                  [myArray removeObjectIdenticalTo:aNumber];
              }
          });
          

          [1]注意:显然,在严格的定义/性能基础上,这不是技术上的快速枚举,但在所有意图和目的上都表现得像它

          【讨论】:

            猜你喜欢
            • 2019-11-23
            • 2017-02-17
            • 1970-01-01
            • 1970-01-01
            • 2019-08-07
            • 2021-02-23
            • 2017-05-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多