Reputation: 1652
Below is my code.
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:@"5"];
[arr addObject:@"7"];
[arr addObject:@"8"];
[arr enumerateObjectsUsingBlock:^(NSString *obj,NSUInteger idx,BOOL *stop) {
[arr replaceObjectAtIndex:idx withObject:@"10"];
}];
The Exception log I got
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x742a580> was mutated while being enumerated.'
*** First throw call stack:
(0x1596012 0x12a3e7e 0x161ecc5 0x158fe1b 0x158fa16 0x158f925 0x2ba4 0x1e87b7 0x1e8da7 0x1e9fab 0x1fb315 0x1fc24b 0x1edcf8 0x25f8df9 0x25f8ad0 0x150bbf5 0x150b962 0x153cbb6 0x153bf44 0x153be1b 0x1e97da 0x1eb65c 0x29fd 0x2925)
libc++abi.dylib: terminate called throwing an exception
The code is working fine while I am using for loop
for (int i = 0 ; i< arr.count; i++) {
[arr replaceObjectAtIndex:i withObject:@"8"];
}
So while I am using enumerateObjectsUsingBlock then I am getting exception. But both are enumerations. Right ? Then why upper code is giving was mutated while being enumerated
exception?
Upvotes: 8
Views: 30610
Reputation: 21966
You can't do this:
[arr enumerateObjectsUsingBlock:^(NSString *obj,NSUInteger idx,BOOL *stop) {
[arr replaceObjectAtIndex:idx withObject:@"10"];
}];
Because you can't mutate the collection while you're enumerating over it.
My suggest is to put all the operations in an operation queue and execute them after the enumeration:
NSOperationQueue* queue= [NSOperationQueue new];
queue.maxConcurrentOperationCount=1;
[queue setSuspended: YES];
[arr enumerateObjectsUsingBlock:^(NSString *obj,NSUInteger idx,BOOL *stop)
{
NSBlockOperation* op=[NSBlockOperation blockOperationWithBlock: ^ (void)
{
[arr replaceObjectAtIndex:idx withObject:@"10"];
}];
[queue addOperation: op];
}];
[queue setSuspended: NO];
[queue waitUntilAllOperationsAreFinished];
Another method would be just to be all the items in another collection and remove them later. That would also be simpler to write.
Upvotes: 7
Reputation: 56625
You can't modify a collection while using fast enumeration. I wrote a post about modifying while enumerating that should be helpful.
Thought looking at it again I never talked about replacing while enumerating. You could still save the indices and objects and use replaceObjectsAtIndexes:withObjects:
after the enumeration.
NSMutableArray *array = // ...
NSMutableIndexSet *indicesForObjectsToReplace = [NSMutableIndexSet new];
NSMutableArray *objectsToReplace = [NSMutableArray new];
[array enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
[indicesForObjectsToRemove addIndex:idx];
[objectsToReplace: @"String you are replacing with"];
}];
[array replaceObjectsAtIndexes:indicesForObjectsToReplace
withObjects:objectsToReplace];
Upvotes: 3
Reputation: 2902
Just add break; after removing your item from your array.
for (NSDictionary *dic in ticketsToBuyArray) {
if ([[[dic objectForKey:@"ticket_id"]stringValue] isEqualToString:[[ticketDictionary objectForKey:@"ticket_id"]stringValue]])
{
[ticketsToBuyArray removeObject:dic];
break;
}
}
Upvotes: 2
Reputation: 1941
I have not much experience with objective-c, but it usually is a feature of enumerator class that prevents changes enumerable object between enumeration iterations.
Enumerator class checks if enumerable object has executed mutating method, like add, remove or replace before returning next instance from the collection. And foreach loop is typically implemented based on enumerator and enumerable classes (or interfaces).
Indexed access to array item is native array method, a basic operation, which doesn't involve any additional mechanism.
Upvotes: 0
Reputation: 46533
Fast enumeration makes the collection Immutable. So whenever you use for(.. in ..)
or block
and try to mutate you will get this error.
Whenever you need to update the collection while looping through it, you need to take mutable collection and use conventional for(;;), while or dowhile loops. for( .. in ..)
makes your mutable collection to immutable at runtime.
Upvotes: 4
Reputation:
Because your logic is flawed. It is not permited to mutate a collection during enumeration. And in the latter case, NSMutableArray doesn't know you're trying to emumerate it, only in the first case. And then it complains, since this is a semantic error. You should generally solve these kinds of problems by mutable copying the array and mutating the copy, then replacing the original one by the updated copy.
Upvotes: 15