NSCry
NSCry

Reputation: 1652

Collection was mutated while being enumerated error in objective C

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

Answers (6)

Ramy Al Zuhouri
Ramy Al Zuhouri

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

David R&#246;nnqvist
David R&#246;nnqvist

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

Atef
Atef

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

Krzysztof Jabłoński
Krzysztof Jabłoński

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

Anoop Vaidya
Anoop Vaidya

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

user529758
user529758

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

Related Questions