NSMutableArray was mutated while being enumerated

Question

I have an array in an old objective-C app that I am using to learn more "complicated" coding. It is back from the old days of OS X and was very much broken. I have gotten it to work (mostly)! However, the app has an NSMutableArray of images, 7 in total. I use a random number generator to insert the images on the screen, some code to allow them to fall, and then, using screen bounds, when they reach "0" on the Y axis they are removed from the array. I initially just had:

if( currentFrame.origin.y+currentFrame.size.height <= 0 )
        {
         [flakesArray removeObject:myItem];

I have read when removing objects from an array it is best practice to iterate in reverse...so I have this bit of code:

for (NSInteger i = myArray.count - 1; i >= 0; i--)
     { //added for for statement
   if( currentFrame.origin.y+currentFrame.size.height <= 0 )
        {
    [myArray removeObjectAtIndex:i];
        }

Sadly both methods result in the same mutated while enumerated error. Am I missing something obvious? If I add an NSLog statement I can get, I think, the index of the item that needs to be removed:

NSLog (@"Shazam! %ld", (long)i);

2017-01-07 14:39:42.086667 MyApp[45995:7500033] Shazam! 2

I have looked through a lot and tried several different methods including this one, which looks to be the most popular with the same error.

Thank you in advance! I will happily provide any additional information!

Adding more: Sorry guys I am not explicitly calling NSFastEnumeration but I have this:

- (void) drawRectCocoa:(NSRect)rect
{
    NSEnumerator* flakesEnum = [flakesArray objectEnumerator];

then

for( i = 0; i < numberToCreate; i++ )
    {
        [self newObject:self];
    }
  while( oneFlake = [flakesEnum nextObject] )

It is here where:

 if( currentFrame.origin.y+currentFrame.size.height <= 0 )
       {
        NSLog (@"Shazam! %i", oneFlake);
 [flakesArray removeObject:oneFlake];
}

Thank you all. I am learning a lot from this discussion!


Show source
| osx   | objective-c   | arrays   | cocoa   2017-01-07 20:01 2 Answers

Answers to NSMutableArray was mutated while being enumerated ( 2 )

  1. 2017-01-07 21:01

    There are two ways to go: (1) collect the objects to remove then remove them with removeObjectsInArray:.

    NSMutableArray *removeThese = [NSMutableArray array];
    for (id item in myArray) {
        if (/* item satisfies some condition for removal */) {
            [removeThese addObject:item];
        }
    }
    
    // the following (and any other method that mutates the array) must be done
    // *outside of* the loop that enumerates the array
    [myArray removeObjectsInArray:removeThese];
    

    Alternatively, reverseObjectEnumeration is tolerant of removes during iteration...

    for (id item in [myArray reverseObjectEnumerator]) {
        if (/* item satisfies some condition for removal */) {
            [myArray removeObject: item];
        }
    }
    
  2. 2017-01-07 21:01

    As per the error, you may not mutate any NSMutableArray (or any NSMutable... collection) while it is being enumerated as part of any fast enumeration loop (for (... in ...) { ... }).

    @danh's answer works as well, but involves allocating a new array of elements. There are two simpler and more efficient ways to filter an array:

    [array filterUsingPredicate:[NSPredicate predicateWithBlock:^(id element, NSDictionary<NSString *,id> *bindings) {
        // if element should stay, return YES; if it should be removed, return NO
    }];
    

    or

    NSMutableIndexSet *indicesToRemove = [NSMutableIndexSet new];
    for (NSUInteger i = 0; i < array.count; i += 1) {
        if (/* array[i] should be removed */) {
            [indicesToRemove addIndex:i];
        }
    }
    
    [array removeObjectsAtIndexes:indicesToRemove];
    

    filterUsingPredicate: will likely be slightly faster (since it uses fast enumeration itself), but depending on the specific application, removeObjectsAtIndexes: may be more flexible.

    No matter what, if you're using your array inside a fast enumeration loop, you will have to perform the modification outside of the loop. You can use filterUsingPredicate: to replace the loop altogether, or you can keep the loop and keep track of the indices of the elements you want to remove for later.

Leave a reply to - NSMutableArray was mutated while being enumerated

◀ Go back