I am using block based enumeration similar to the following code:
[[[rows objectForKey:self.company.coaTypeCode] objectForKey:statementType]
enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(id coaItem, NSUInteger idx, BOOL *stop) {
// block code here
}]
I would like to remove some of the objects during the enumeration process depending on the their object values.
How could I do this? I know that manipulating an mutable array or dictionary (NSMutableArray or NSMutableDictionary) during enumeration is usually not possible.
What would be the best way to implement this?
Thank you!
Since you can't remove objects from an array or dictionary during enumeration, you'll have to accumulate the items you want to delete, and then delete them all after the enumeration.
If you're dealing with an array, you can just accumulate the indices.:
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];
Since the order of the keys in an NSDictionary is undefined, for an NSMutableDictionary you'll have to accumulate keys instead:
NSMutableArray *keysToDelete = [NSMutableArray array];
for (id obj in [yourDictionary keyEnumerator]) {
//do stuff with obj
if (shouldBeDeleted(obj)) {
[keysToDelete addObject:obj];
}
}
[yourDictionary removeObjectsForKeys:keysToDelete];
It's the same thing if you're enumerating with a block. Declare the enumerator in the same scope where you declare the block and it will be retained and just work.
Also worth looking at this question from 3 years ago: Best way to remove from NSMutableArray while iterating?.
Whether you build up an index set during enumeration, or modify the array itself during enumeration, you will have to give up NSEnumerationConcurrent
, because most Cocoa objects cannot safely be modified simultaneously from multiple threads.
Anyway, the simplest (but maybe not most efficient) approach is to just enumerate a copy of the container.
For an array, you can enumerate a copy in reverse. I assume that as each item is being enumerated, you may decide to remove that item, but not other items previously enumerated or yet to be enumerated.
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];
}]
You have to enumerate the array in reverse to avoid changing the not-yet-enumerated part of the array.
For a dictionary, you can just enumerate a copy with no special options:
NSMutableDictionary *dictionary = someDictionary;
[[dictionary copy] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([self object:obj isTooUglyToExistAtKey:key])
[dictionary removeObjectForKey:key];
}];
Another option, with an array, is to use a conventional for
loop, with the array's count
as the limit. Then one needs to be cognizant of whether an element is removed from a location <=
the index (in which case the index should be decremented) or >
than the index (in which case the index is left unmodified other than the for
statement's increment).
For a dictionary you can first create an array with allKeys
, and then iterate through the array. In this case no fiddling with index values is required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With