Как удалить элементы в NSMutableArray или NSMutableDictionary во время перечисления? - PullRequest
12 голосов
/ 25 февраля 2012

Я использую перечисление на основе блоков, подобное следующему коду:

[[[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType] 
    enumerateObjectsWithOptions:NSEnumerationConcurrent 
                     usingBlock:^(id coaItem, NSUInteger idx, BOOL *stop) { 
// block code here
}]

Я хотел бы удалить некоторые объекты во время процесса перечисления в зависимости от значений их объектов.

Как я мог это сделать? Я знаю, что манипулирование изменяемым массивом или словарем (NSMutableArray или NSMutableDictionary) во время перечисления обычно невозможно.

Как лучше всего это реализовать?

Спасибо!

Ответы [ 3 ]

39 голосов
/ 25 февраля 2012

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

Если выимея дело с массивом, вы можете просто накапливать индексы .:

NSMutableIndexSet *indexesToDelete = [NSMutableIndexSet indexSet];
NSUInteger currentIndex = 0;

for (id obj in yourArray) {
    //do stuff with obj
    if (shouldBeDeleted(obj)) {
        [indexesToDelete addIndex:currentIndex];
    }
    currentIndex++;
}

[yourArray removeObjectsAtIndexes:indexesToDelete];

Поскольку порядок ключей в NSDictionary не определен, для NSMutableDictionary вам придется вместо этого накапливать ключи:

NSMutableArray *keysToDelete = [NSMutableArray array];

for (id obj in [yourDictionary keyEnumerator]) {
    //do stuff with obj
    if (shouldBeDeleted(obj)) {
        [keysToDelete addObject:obj];
    }
}

[yourDictionary removeObjectsForKeys:keysToDelete];

То же самое, если вы перечисляете с блоком.Объявите перечислитель в той же области, где вы объявляете блок, и он будет сохранен и просто будет работать.

Также стоит посмотреть на этот вопрос 3 года назад: Лучший способ удалить из NSMutableArray во время итерации?.

8 голосов
/ 25 февраля 2012

Если вы создаете набор индексов во время перечисления или изменяете сам массив во время перечисления, вам придется отказаться от NSEnumerationConcurrent, потому что большинство объектов Какао нельзя безопасно изменять одновременно из нескольких потоков.

В любом случае, самый простой (но, возможно, не самый эффективный) подход - просто перечислить копию контейнера.

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

NSMutableArray *array = [[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType];
[[array copy] enumerateObjectsWithOptions: NSEnumerationReverse 
    usingBlock:^(id coaItem, NSUInteger idx, BOOL *stop) {
    if ([self objectIsTooUglyToExist:coaItem])
        [array removeObjectAtIndex:idx];
}]

Необходимо перечислить массив в обратном порядке, чтобы избежать измененияеще не перечислимая часть массива.

Для словаря вы можете просто перечислить копию без специальных опций:

NSMutableDictionary *dictionary = someDictionary;
[[dictionary copy] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    if ([self object:obj isTooUglyToExistAtKey:key])
        [dictionary removeObjectForKey:key];
}];
1 голос
/ 25 февраля 2012

Другой вариант с массивом - использовать обычный цикл for с пределом массива count.Затем необходимо знать, удален ли элемент из местоположения <= индекса (в этом случае индекс должен быть уменьшен) или >, чем индекс (в этом случае индекс остается неизменным, кроме for приращение оператора).

Для словаря вы можете сначала создать массив с allKeys, а затем выполнить итерацию по массиву.В этом случае не нужно возиться со значениями индекса.

...