web-dev-qa-db-fra.com

Éviter "NSArray a été muté lors de l'énumération"

J'ai une NSMutableArray qui stocke les jonctions de la souris pour une simulation physique Box2d. Lorsque plusieurs joueurs jouent, je vais recevoir des exceptions indiquant 

NSArray a été muté pendant l'énumération

Je sais que cela est dû au fait que je supprime des objets du tableau tout en énumérant les éléments à travers lui, ce qui invalide l’énumération. 

Ce que je veux savoir, c'est quelle est la meilleure stratégie pour résoudre ce problème à l'avenir? J'ai vu quelques solutions en ligne: @synchronized, copier le tableau avant d'énumérer ou de placer le joint tactile dans un tableau de mémoire pour suppression ultérieure (ce que je ne suis pas sûr que cela fonctionnerait, car je dois supprimer le point de la souris du tableau immédiatement après le retirer du monde). 

40
glenstorey

Vous pouvez toujours itérer sans un énumérateur . Ce qui signifie une boucle régulière pour , et lorsque vous supprimez un objet: - décrémentez la variable d'index et continue; . si vous mettez en cache le nombre du tableau avant d'entrer dans la boucle for, assurez-vous de le décrémenter également lors de la suppression d'un objet.

Quoi qu'il en soit, je ne vois pas pourquoi un tableau avec des objets pour une suppression ultérieure poserait un problème. Je ne connais pas la situation exacte dans laquelle vous vous trouvez ni les technologies impliquées, mais théoriquement, il ne devrait pas y avoir de problème… .. Parce que dans la plupart des cas, lorsque vous utilisez cette méthode, vous ne pouvez rien faire lors du premier dénombrement, travail lors de l'énumération du tableau de suppression . Et si, dans la première énumération, vous vérifiez quelque chose again contre le même tableau et que vous devez savoir que les objets ne sont plus présents, vous pouvez simplement ajoutez une vérification pour voir si elles sont dans la matrice de suppression.

Quoi qu'il en soit, j'espère que j'ai aidé . Bonne chance!

47
daniel.gindi

Vous pouvez faire quelque chose comme ça:

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

La méthode la plus simple consiste à énumérer en arrière dans le tableau, ce qui signifie que l’index suivant ne sera pas affecté lorsque vous supprimez un objet.

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

L'opération de verrouillage (@synchronisée) est beaucoup plus rapide que la copie répétée d'un tableau entier. Bien sûr, cela dépend du nombre d’éléments du tableau et de sa fréquence d’exécution . Imaginez que 10 threads exécutent cette méthode simultanément:

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

Il copie n + 1 objets à chaque fois. Vous pouvez utiliser lock ici, mais que se passe-t-il s'il y a 100 000 éléments à itérer? Le tableau sera verrouillé jusqu'à la fin de l'itération et les autres threads devront attendre que le verrou soit libéré. Je pense que copier un objet ici est plus efficace, mais cela dépend aussi de la taille de cet objet et de ce que vous faites en itération. Lock devrait toujours être gardé pour la plus courte période de temps. Je voudrais donc utiliser le verrou pour quelque chose comme ça.

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

Utiliser une boucle for au lieu d’énumérer est acceptable. Mais une fois que vous commencez à supprimer des éléments de tableau, faites attention si vous utilisez des threads. Il ne suffit pas de décrémenter le compteur car vous supprimez peut-être les éléments incorrects ..__ Une des méthodes correctes consiste à créer une copie du tableau, à itérer la copie et à supprimer de l'original.

edc1591 est la bonne méthode.

[Brendt: besoin de supprimer de l'original tout en parcourant la copie]

1
Komposr

J'ai eu le même problème, et la solution consiste à énumérer la copie et à muter l'original, comme indiqué correctement par edc1591. J'ai essayé le code et je ne reçois plus l'erreur. Si l'erreur persiste, cela signifie que la copie (au lieu de l'original) est toujours en cours de mutation dans la boucle 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

Tout ce qui précède n'a pas fonctionné pour moi, mais cela a:

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

Remarque: cela supprimera tout. Si vous devez sélectionner les éléments à supprimer, cela ne fonctionnera pas.

1
averageJoe

J'ai rencontré cette erreur et dans mon cas, c'était à cause de la situation multi-threaded. Un thread énumère un tableau, tandis qu'un autre thread supprime les objets du même tableau au même moment. J'espère que ça aide quelqu'un.

0
Vishal Chaudhry

Peut-être avez-vous supprimé ou ajouté des objets à votre mutableAray en l'énumérant .

0
Shamsiddin