Как удалить элементы в NSMutableArray или NSMutableDictionary во время перечисления?
Я использую перечисление на основе блоков, похожее на следующий код:
[[[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType]
enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(id coaItem, NSUInteger idx, BOOL *stop) {
// block code here
}]
Я хочу удалить некоторые объекты во время процесса перечисления в зависимости от их значений объектов.
Как я могу это сделать? Я знаю, что манипулирование изменяемым массивом или словарем (NSMutableArray или NSMutableDictionary) во время перечисления обычно невозможно.
Каким будет лучший способ реализовать это?
Спасибо!
Ответы
Ответ 1
Поскольку вы не можете удалить объекты из массива или словаря во время перечисления, вам нужно будет накапливать элементы, которые вы хотите удалить, а затем удалить их после перечисления.
Если вы имеете дело с массивом, вы можете просто накапливать индексы.:
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 равен undefined, для 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 во время итерации?.
Ответ 2
Создаете ли вы набор индексов во время перечисления или сами измените массив во время перечисления, вам придется отказаться от NSEnumerationConcurrent
, потому что большинство объектов Cocoa нельзя безопасно модифицировать одновременно из нескольких потоков.
В любом случае, самый простой (но, возможно, не самый эффективный) подход - просто перечислить копию контейнера.
Для массива вы можете перечислить копию в обратном порядке. Я предполагаю, что по мере перечисления каждого элемента вы можете решить удалить этот элемент, но не другие элементы, ранее перечисленные или еще подлежащие перечислению.
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];
}];
Ответ 3
Другим вариантом с массивом является использование обычного цикла for
с ограничением массива count
. Затем нужно знать, удаляется ли элемент из местоположения <=
индекса (в этом случае индекс должен быть уменьшен) или >
, чем индекс (в этом случае индекс остается неизмененным, отличным от for
).
Для словаря вы можете сначала создать массив с allKeys
, а затем перебрать массив. В этом случае не требуется ничьи с индексами.