it-swarm.com.ru

Избегать "NSArray был мутирован во время перечисления"

У меня есть NSMutableArray, которая хранит мышиные соединения для симуляции физики Box2d. При использовании более одного пальца, я получу исключения 

NSArray был видоизменён при перечислении

Я знаю, что это потому, что я удаляю объекты из массива, а также перечисляю его, аннулируя перечисление. 

Я хочу знать, какова лучшая стратегия для решения этой проблемы в будущем? В Интернете я видел несколько решений: @synchronized, копирование массива перед перечислением или помещение сенсорного соединения в массив мусора для последующего удаления (что, я не уверен, сработает, потому что мне нужно сразу же удалить точку мыши из массива после удаляя его из мира). 

40
glenstorey

Вы всегда можете выполнять итерации без перечислителя . Это означает обычный цикл for , а при удалении объекта: - уменьшите индексную переменную и продолжите; . если вы кэшируете счетчик массива перед входом в цикл 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];
36
edc1591

Самый простой способ - перечислить в обратном направлении через массив, что означает, что следующий индекс не будет затронут при удалении объекта.

for (NSObject *object in [mutableArray reverseObjectEnumerator]) {
    // it is safe to test and remove the current object      
    if (AddTestHere) {
        [savedRecentFiles removeObject: object];
    }
}
32
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 объектов. Вы можете использовать блокировку здесь, но что, если есть 100k элементов для итерации? Массив будет заблокирован до завершения итерации, и другим потокам придется ждать, пока не будет снята блокировка. Я думаю, что копирование объекта здесь более эффективно, но это также зависит от того, насколько велик этот объект и что вы делаете в итерации. Блокировка всегда должна храниться в течение кратчайшего периода времени. Поэтому я бы использовал блокировку для чего-то вроде этого.

- (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 вместо перечисления нормально. Но как только вы начнете удалять элементы массива, будьте осторожны, если вы используете потоки. Недостаточно просто уменьшить счетчик, так как вы, возможно, удаляете неправильные вещи .. Один из правильных способов - создать копию массива, повторить копию и удалить из оригинала.

edc1591 является правильным методом.

[Брендт: нужно удалить из оригинала при переборе копии]

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

Я сталкивался с этой ошибкой, и в моем случае это было из-за многопоточной ситуации. Один поток перечислял массив, в то время как другой поток одновременно удалял объекты из того же массива. Надеюсь, это кому-нибудь поможет.

0
Vishal Chaudhry

Возможно, вы удалили или добавили объекты в свой mutableAray при перечислении его ... В моей ситуации в этом случае возникла ошибка.

0
Shamsiddin