Avoiding "NSArray was mutated while being enumerated"

50,784

Solution 1

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!

Solution 2

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];

Solution 3

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];
    }
}

Solution 4

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
}

Solution 5

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.

Share:
50,784

Related videos on Youtube

glenstorey
Author by

glenstorey

We have released BookBot, Silent Light, Math Shake and Times Table Galaxy for iOS and Purrfect Memory of OSX - http://topstoreyapps.com ICT Infrastructure lead for innovative Apple Distinguished School in Papamoa, New Zealand.  ADE Class of 2015.

Updated on December 11, 2020

Comments

  • glenstorey
    glenstorey over 3 years

    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).

  • Jarrod
    Jarrod almost 12 years
    "Anyway, hope I helped"; Certainly helped me, A LOT! Thank you!
  • Brendt
    Brendt over 11 years
    This is not true. I still get a mutated while being enumerated exception.
  • 最白目
    最白目 about 11 years
    thanks for the hint to delete from the original while iterating the copy, it wasnt clear to me.
  • Victor Engel
    Victor Engel over 10 years
    This does not work for the same problem but with NSMutableSet rather than arrays. Do you have a suggestion for that scenario?
  • daniel.gindi
    daniel.gindi over 10 years
    @VictorEngel yes, create a separate set with all the item you wish to remove, then use "minusSet" on the original set (or a mutableCopy of it) with the second set as the argument. :)
  • Tim Arnold
    Tim Arnold about 10 years
    Should tempArray instead be initialized like NSArray *tempArray = [NSArray arrayWithArray:yourArray]? It seems like this snippet of code wouldn't do what the OP intended
  • edc1591
    edc1591 about 10 years
    @TimArnold I'm pretty sure that would give the same result.
  • Kaveh Vejdani
    Kaveh Vejdani about 10 years
    To empty an array you don't need to delete the objects one by one. You can just say myArray = [NSMutableArray init]; Mutating isn't just about deleting, it also includes modifying the objects, which is usually what you want to do. In either case, the right way to "enumerate and mutate" an array is to create a copy of your array first and use that copy to enumerate the objects while you mutate the original array as intended.
  • Evgen Bodunov
    Evgen Bodunov over 9 years
    @Brendt please, show your snippet of code. This answer is seems correct for me. Because we work with different container and it stores all objects from yourArray retained.
  • Nicolas Miari
    Nicolas Miari over 9 years
    setObject:forKey is a method of dictionaries, not arrays (also, it must be the mutable version). Otherwise, the method is correct.
  • Allen
    Allen over 9 years
    I got this error when use block enumerate method in iOS6.
  • Itachi
    Itachi over 8 years
    Use for (int = 0; i < yourArray.count; i++) { /* It's safe to remove objects from yourArray here.*/ } , this will not use the enumerator of array.
  • IrelDev
    IrelDev about 3 years
    simplest & efficient answer
  • saagarjha
    saagarjha over 2 years
    This is not correct. From the reverseObjectEnumerator documentation: "When you use this method with mutable subclasses of NSArray, you must not modify the array during enumeration."
  • martinjbaker
    martinjbaker over 2 years
    That's a general warning but obviously it is more subtle than that. Modifying the NSMutableArray by inserting or deleting elements EARLIER than the current index will create problems but that isn't what is being done here. Trust me I've been using this technique for 6+ years without a single issue. It works just fine.
  • saagarjha
    saagarjha over 2 years
    Just because it "works fine" does not mean it's correct. You can call private API it too will "work fine" until Apple decides to change it, and then you will crash. You're relying on an internal implementation detail and explicitly doing what the documentation tells you not to do. Use one of the supported ways of doing this, please.
  • martinjbaker
    martinjbaker over 2 years
    Don't be silly. What "internal implementation detail" am I relying on?