Подтвердить что ты не робот

Как удалить элементы в NSMutableArray или NSMutableDictionary во время перечисления?

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

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

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

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

Каким будет лучший способ реализовать это?

Спасибо!

4b9b3361

Ответ 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, а затем перебрать массив. В этом случае не требуется ничьи с индексами.