Evitando que “NSArray sofreu uma mutação ao ser enumerado”

Eu tenho um NSMutableArray que armazena mousejoints para uma simulação de física Box2d. Ao usar mais de um dedo para jogar, receberei exceções

NSArray sofreu uma mutação ao ser enumerado

Eu sei que isso é porque eu estou excluindo objects da matriz ao mesmo tempo, enumerando através dele, invalidando o enum.

O que eu quero saber é qual é a melhor estratégia para resolver isso daqui para frente? Vi algumas soluções on-line: @synchronized , copiando a matriz antes de enumerar ou colocar a junit de toque em uma matriz de lixo para exclusão posterior (que não tenho certeza se funcionaria, porque preciso remover o mousejoint da matriz diretamente depois de removê-lo do mundo).

Você sempre pode iterar sem um enumerador. O que significa um laço for regular, e quando você remove um object: – decrementa a variável de índice e continua; . Se você estiver armazenando em cache a contagem da matriz antes de inserir o loop for, certifique-se de diminuir isso também ao remover um object.

De qualquer forma, eu não vejo porque um array com objects para remoção posterior seria um problema. Eu não sei a situação exata que você está tendo e as tecnologias envolvidas, mas, teoricamente, não deveria haver um problema. Como na maioria dos casos, ao usar esse método, você não pode fazer nada na primeira enumeração e executar o trabalho real ao enumerar a matriz de remoção. E se você tiver uma situação na qual na primeira enumeração você está verificando algo novamente na mesma matriz e precisa saber que os objects não estão mais lá, basta adicionar uma verificação para ver se eles estão na matriz de remoção.

De qualquer forma, espero ter ajudado. Boa sorte!

Você pode fazer algo assim:

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

A maneira mais simples é enumerar o array de trás para a frente, o que significa que o próximo índice não será afetado quando você remover um object.

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

A operação de bloqueio (@synchronized) é muito mais rápida do que copiar todo o array repetidamente. Claro que isso depende de quantos elementos o array possui e com que frequência ele é executado. Imagine que você tenha 10 threads executando este método simultaneamente:

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

Está copiando n + 1 objects a cada vez. Você pode usar o bloqueio aqui, mas e se houver 100k elementos para iterar? Array será bloqueado até que a iteração seja concluída, e outros threads terão que esperar até que o bloqueio seja liberado. Acho que copiar object aqui é mais efetivo, mas também depende de quão grande é esse object e o que você está fazendo na iteração. O bloqueio deve ser sempre mantido pelo menor período de tempo. Então eu usaria bloqueio para algo assim.

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

Todo o acima não funcionou para mim, mas isso fez:

  while ([myArray count] > 0) { [:[myArray objectAtIndex:0]]; } 

Nota: isto irá apagar tudo. Se você precisar escolher quais itens precisam ser excluídos, isso não funcionará.

Usando um loop for em vez de enumerar é ok. Mas quando você começar a excluir os elementos da matriz, tenha cuidado se estiver usando threads. Não é suficiente apenas diminuir o contador, pois você pode estar excluindo as coisas erradas. Uma das formas corretas é criar uma cópia da matriz, iterar a cópia e excluir do original.

edc1591 é o método correto.

[Brendt: precisa excluir do original enquanto percorre a cópia]

 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]; 

Eu tive o mesmo problema, e a solução é enumerar a cópia e alterar o original, como edc1591 corretamente mencionado. Eu tentei o código e não estou mais recebendo o erro. Se você ainda estiver recebendo o erro, isso significa que você ainda está alterando a cópia (em vez do original) dentro do loop for.

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

Talvez você tenha excluído ou adicionado objects ao seu mutableAray ao enumerá-lo. Na minha situação, o erro apareceu neste caso.

Eu me deparei com esse erro e no meu caso foi por causa da situação multi-threaded. Um thread estava enumerando um array enquanto outro thread removia objects do mesmo array ao mesmo tempo. Espero que isso ajude alguém.