38

I kinda stumbled into the error where you try to remove objects from an NSMutableArray while other objects is being added to it elsewhere. To keep it simple, i have no clue on how to fix it. Here is what i'm doing:

I have 4 timers calling 4 different methods that adds an object to the same array. Now, when i press a certain button, i need to remove all objects in the array (or at least some). So i tried to first invalidate all the 4 timers, and then do the work i want with the array, and then fire up the timers. I thought this would've worked since i am not using the timers anymore to enumerate through the array, but seemingly it doesn't work.

Any suggestions here?

u0b34a0f6ae
  • 48,117
  • 14
  • 92
  • 101
Seerex
  • 591
  • 1
  • 9
  • 21
  • This is also happening to me, but I am not removing anything from my NSMutableArray. I am however, adding items to it in a background thread. It only throws this error maybe once every 50 or 100, adds...and this is the only thread that ever touches the array in the entire app... – Jesse Apr 20 '14 at 05:29

6 Answers6

112

It's nothing to do with your timers. Because (I assume) your timers are all working on the same thread as your modifying method you don't need to stop and start them. iOS doesn't use an interrupt model for timer callbacks, they have to wait their turn just like any other event :)

You're probably doing something like

for (id object in myArray)
   if (someCondition)
       [myArray removeObject:object];

You can't edit a mutable array while you're going through it so you need to make a temporary array to hold the things you want to remove

// Find the things to remove
NSMutableArray *toDelete = [NSMutableArray array];
for (id object in myArray)
   if (someCondition)
       [toDelete addObject:object];

// Remove them
[myArray removeObjectsInArray:toDelete];
deanWombourne
  • 38,189
  • 13
  • 98
  • 110
  • I think the last two lines should read "`for (id object in toDelete) [myArray removeObject:object];`" – Tobias Klüpfel Jan 12 '12 at 11:10
  • @TobiasKlüpfel - you're absolutely right - I've edited my answer. – deanWombourne Jan 12 '12 at 11:13
  • Just use `-removeObjectsInArray:`. Also, if accessing via indices you can modify while iterating over it. – Georg Fritzsche Jan 12 '12 at 11:29
  • @GeorgFritzsche - the first part of your comment is excellent advice; I've edited my answer :) – deanWombourne Jan 12 '12 at 11:31
  • @GeorgFritzsche - However, the second part is very dangerous; if you're using an index to iterate through an array though a 100 item array and remove one, your app will crash with an exception when it asks for item at index 100. It will also skip the item just after you have done the remove! – deanWombourne Jan 12 '12 at 11:34
  • The point about the second part was not that it's preferrable, but that you can edit a mutable array while iterating over it - just not when using enumeration. – Georg Fritzsche Jan 12 '12 at 11:36
  • 1
    I prefer playing it safe; too many subtle bugs for my liking! – deanWombourne Jan 12 '12 at 11:39
  • I used this one, thanks a lot chap, it worked like a charm :) So basically, what you can't do is enumerate through an array, and remove objects in that same array during the enumeration? correct? :) – Seerex Jan 12 '12 at 12:53
  • correct :) (And this rule is true for _any_ mutable collection i.e. NNSMutableSet etc) – deanWombourne Jan 12 '12 at 14:44
  • @daidai - I think your edit was too hastily rejected so I've made the change anyway - my answer compiles now :) Good spot! – deanWombourne Mar 18 '13 at 11:08
  • Is it OK to use normal for loop, and take care of index when removing object from NSArray ? – Paweł Brewczynski May 17 '14 at 12:44
  • 1
    @bluesm Technically, yes that would work. However, it woudln't get past peer review in any team I'm in :) The code would look horribly complicated and I bet there would be an off-by-1 bug somewhere in there! Unless there's a vital speed/memory increase you get from that, I'd go for clearer code every time. – deanWombourne May 20 '14 at 10:30
  • @deanWombourne, +1 for this `You can't edit a mutable array while you're going through ` – Sushil Sharma May 21 '15 at 07:58
24

You can do it this way:

for (id object in [myArray copy])
   if (someCondition)
       [myArray removeObject:object];

Like @deanWombourne said, "you can't edit a mutable array while you're going through it", so what i'm doing here is to create an autoreleased copy of your original array to enumerate the objects, so you can safely remove anything you want.

More clear and less boiler code (i think!).

Update: Removed autorelease call since this was an old answer, pre ARC.

ferostar
  • 7,074
  • 7
  • 38
  • 61
  • Very nice :) Might be a performance problem if your `myArray` is very large and you're only removing a few items but I certainly like it's elegance! – deanWombourne Jan 12 '12 at 14:46
  • Most elegant here, and of course `autorelease` is not needed with ARC. – mojuba Jun 28 '13 at 16:08
  • In addition to the performance issue @deanWombourne mentioned earlier, using `[myArray copy]` for each iteration could slow down your program. I would strongly recommend creating an `NSArray` instance (e.g., `NSArray *tempArray = [myArray copy];`) before the for loop and using that instead. – pxpgraphics Nov 24 '14 at 19:01
  • 2
    Hi @pxpgraphics - If you were doing a `for(n=0;n – deanWombourne Nov 25 '14 at 10:08
5

You can use the @synchronized() directive to lock the array while you mutate it:

if (someCondition) {
    @synchronized(yourArray) {
        [yourArray removeObject:someObject];
    }
}

More info at http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocThreading.html

Niklas Berglund
  • 3,563
  • 4
  • 32
  • 32
0

Though above all are true... I would share my experience with

mutated while being enumerated

What I was doing was simple but totally error-full:

for (id obj  in d.dataDashBoardGraph) {
    [tmpData addObject:[obj valueForKey:[[dataToShow[i] componentsSeparatedByString:@"_"] objectAtIndex:1]]];

    ...
}

Even this caused mutated being enumerated error. To get rid of it:

for (id obj  in d.dataDashBoardGraph) {
   NSString *data = [dataToShow[i] copy];
   [tmpData addObject:[obj valueForKey:[[data componentsSeparatedByString:@"_"] objectAtIndex:1]]];

    ... 
}

Then it worked perfectly.

pkamb
  • 33,281
  • 23
  • 160
  • 191
0

You can try:

for (id object in [myArray reverseObjectEnumerator])
if (someCondition)
   [myArray removeObject:object];

If you remove object at index x => The index of objects at index x + 1, x + 2.... will be changed. So when you use reverseObjectEnumerator , the index of objects in array after remove some objects will still correct.

Hope this help. (accepted answer is a clear solution.)

Nhat Dinh
  • 3,378
  • 4
  • 35
  • 51
-1

NSMutableArray can not be mutated while being enumerated, you may create a minor delay before calling your action:

 for(id key in resultsDictionary) {
                if ([key isEqual:whichButtonString]) {
                   // [resultsDictionary removeObjectForKey:whichButtonString];
                    [self performSelector:@selector(removeKeyFromDictionary:) withObject:whichButtonString afterDelay:1.0];
                }
            }

Then

-(void) removeKeyFromDictionary : (NSString *) incomingString {
[resultsDictionary removeObjectForKey:incomingString];

}