web-dev-qa-db-ja.com

「NSArrayは列挙中に変更されました」の回避

Box2d物理シミュレーションのマウスジョイントを格納するNSMutableArrayがあります。複数の指を使ってプレイすると、例外が表示されます

列挙中にNSArrayが変異しました

これは、配列からオブジェクトを削除すると同時に、列挙を無効にしているためです。

私が知りたいのは、これを解決するための最善の戦略は何ですか?オンラインでいくつかのソリューションを見てきました:@synchronized、列挙する前に配列をコピーするか、後で削除するためにタッチジョイントをガベージ配列に配置します(ワールドから削除した直後に配列からmousejointを削除する必要があるので、うまくいきません)。

43
glenstorey

列挙子なしでいつでも反復できます。これは、通常のforループを意味し、オブジェクトを削除するとき:-インデックス変数をデクリメントし、continue;にします。 forループに入る前に配列のカウントをキャッシュしている場合は、オブジェクトを削除するときにもカウントを減らすようにしてください。

とにかく、後で削除するためのオブジェクトを持つ配列が問題になる理由はわかりません。あなたが抱えている正確な状況と関連する技術はわかりませんが、理論的には問題はないはずです。ほとんどの場合、このメソッドを使用すると、最初の列挙では何もできず、削除配列を列挙するときに実際の作業を実行できるためです。また、最初の列挙で同じ配列に対して何かagainをチェックしていて、オブジェクトがもう存在しないことを知る必要がある場合、削除配列内にあるかどうかを確認するためのチェックを追加できます。

とにかく、私が助けたことを願っています。がんばろう!

47
daniel.gindi

次のようなことができます:

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

最も簡単な方法は、配列を介してbackwardsを列挙することです。つまり、オブジェクトを削除しても次のインデックスは影響を受けません。

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

ロック(@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個のオブジェクトをコピーしています。ここでロックを使用できますが、反復する要素が10万ある場合はどうでしょうか?配列は反復が完了するまでロックされ、他のスレッドはロックが解除されるまで待機する必要があります。ここでオブジェクトをコピーする方が効果的だと思いますが、それはそのオブジェクトがどれだけ大きいか、そして繰り返しで何をしているかにも依存します。ロックは常に最短時間維持する必要があります。だから私はこのような何かのためにロックを使用します。

- (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
DaNY

列挙する代わりにforループを使用しても問題ありません。ただし、配列要素の削除を開始したら、スレッドを使用している場合は注意してください。間違ったものを削除している可能性があるため、カウンターをデクリメントするだけでは十分ではありません。正しい方法の1つは、アレイのコピーを作成し、コピーを繰り返して元のコピーから削除することです。

edc1591が正しい方法です。

[Brendt:コピーを繰り返し処理している間、オリジナルから削除する必要がある]

1
Komposr

私は同じ問題を抱えていましたが、解決策は、edc1591が正しく言及しているように、コピーを列挙し、オリジナルを変更することです。コードを試してみましたが、エラーはもう発生していません。それでもエラーが発生する場合は、forループ内で(オリジナルではなく)コピーを変更していることを意味します。

NSArray *copyArray = [[NSArray alloc] initWithArray:originalArray];

for (id obj in copyArray) { 
    // Tweak obj as you will 
    [originalArray setObject:obj forKey:@"kWhateverKey"];
}
1
Kaveh Vejdani
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];
1
Kursat Turkay

上記はすべてうまくいきませんでしたが、これはうまくいきました:

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

注:これにより、すべて削除されます。削除する必要があるアイテムを選択する必要がある場合、これは機能しません。

1
averageJoe

私はこのエラーに遭遇しましたが、私の場合はマルチスレッドの状況が原因でした。 1つのスレッドが配列を列挙している間に、別のスレッドが同じ配列から同時にオブジェクトを削除しました。これが誰かを助けることを願っています。

0
Vishal Chaudhry

列挙中にmutableArayにオブジェクトを削除または追加した可能性があります。私の状況では、この場合エラーが発生しました。

0
Shamsiddin