Reputation: 5154
I have an NSMutableArray
that stores mousejoints for a Box2d physics simulation. When using more than one finger to play I'll get exceptions stating
NSArray was mutated while being enumerated
I know this is because I'm deleting objects from the array while also enumerating through it, invalidating the enum.
What I want to know is what is the best strategy to solve this going forward? I've seen a few solutions online: @synchronized
, copying the array before enumerating or putting the touch joint into a garbage array for later deletion (which I'm not sure would work, because I need to remove the mousejoint from the array straight after removing it from the world).
Upvotes: 46
Views: 54804
Reputation: 3831
NSMutableArray * founded = [NSMutableArray new];
for (XYZ * item in searchableArray) {
if (item.xValue == 1) {
[founded addObject:item];
}
}
// NSSet * set = [[NSSet alloc] initWithArray:founded];
// [searchableArray removeItems:set];
Hint: in my case, the searchableArray is part of managed object
Upvotes: 0
Reputation: 1484
The simplest way is to enumerate backwards through the array which means the next index won't be affected when you remove an object.
for (NSObject *object in [myMutableArray reverseObjectEnumerator]) {
// it is safe to test and remove the current object
if (AddTestHere) {
[myMutableArray removeObject: object];
}
}
Upvotes: 39
Reputation: 2874
I have come across this error and in my case it was because of the multi-threaded situation. One thread was enumerating an array while another thread removed objects from the same array at the same time. Hope this helps someone.
Upvotes: 0
Reputation: 3496
You can always iterate without an enumerator. Which means a regular for loop, and when you remove an object:- decrement the index variable and continue;. if you are caching the array's count before entering the for-loop, then make sure you decrement that too when removing an object.
Anyway, I do not see why an array with objects for later removal would be a problem. I do not know the exact situation which you are having and the technologies involved, but theoretically there should not be a problem. Because in most cases when using this method you can just do nothing in the first enumeration, and do the real work when enumerating the removal array. And if you have a situation where in the first enumeration you are checking something again against the same array and you need to know that the objects are not there anymore, you can just add a check to see if they are in the removal array.
Anyway, hope I helped. Good luck!
Upvotes: 49
Reputation: 224
I had the same problem, and the solution is to enumerate the copy and mutate the original, as edc1591 correctly mentioned. I've tried the code and I'm not getting the error anymore. If you're still getting the error, it means you are still mutating the copy (instead of the original) inside the for loop.
NSArray *copyArray = [[NSArray alloc] initWithArray:originalArray];
for (id obj in copyArray) {
// tweak obj as you will
[originalArray setObject:obj forKey:@"kWhateverKey"];
}
Upvotes: 1
Reputation: 2808
for(MyObject *obj in objQueue)
{
//[objQueue removeObject:someof];//NEVER removeObject in a for-in loop
[self dosomeopearitions];
}
//instead of remove outside of the loop
[objQueue removeAllObjects];
Upvotes: 2
Reputation: 2281
Maybe you deleted or added objects to your mutableAray while enumerating it. In my situation error appeared in this case.
Upvotes: 0
Reputation: 1956
Using a for loop instead of enumerating is ok. But once you start deleting array elements, be careful if you're using threads. It's not enough to just decrement the counter as you may be deleting the wrong things. One of the correct ways is to create a copy of the array, iterate the copy and delete from the original.
edc1591 is the correct method.
[Brendt: need to delete from the original while iterating through the copy]
Upvotes: 1
Reputation: 11
All the above did not work for me, but this did:
while ([myArray count] > 0)
{
[<your delete method call>:[myArray objectAtIndex:0]];
}
Note: this will delete it all. If you need to pick which items need to be deleted, then this will not work.
Upvotes: 1
Reputation: 191
Lock (@synchronized) operation is much faster then copying entire array over and over again. Of course this depends on how many elements the array has, and how often is it executed. Imagine you have 10 threads executing this method simultaneously:
- (void)Callback
{
[m_mutableArray addObject:[NSNumber numberWithInt:3]];
//m_mutableArray is instance of NSMutableArray declared somewhere else
NSArray* tmpArray = [m_mutableArray copy];
NSInteger sum = 0;
for (NSNumber* num in tmpArray)
sum += [num intValue];
//Do whatever with sum
}
It is copying n+1 objects each time. You can use lock here, but what if there is 100k elements to iterate? Array will be locked until iteration is completed, and other threads will have to wait until lock is released. I think copying object here is more effective, but it also depends on how big that object are and what are you doing in iteration. Lock should be always keept for the shortest period of time. So I would use lock for something like this.
- (void)Callback
{
NSInteger sum = 0;
@synchronized(self)
{
if(m_mutableArray.count == 5)
[m_mutableArray removeObjectAtIndex:4];
[m_mutableArray insertObject:[NSNumber numberWithInt:3] atIndex:0];
for (NSNumber* num in tmpArray)
sum += [num intValue];
}
//Do whatever with sum
}
Upvotes: 6
Reputation: 10182
You can do something like this:
NSArray *tempArray = [yourArray copy];
for(id obj in tempArray) {
//It's safe to remove objects from yourArray here.
}
[tempArray release];
Upvotes: 37