iosobjective-ccocoa-touchnsmutablearrayfast-enumeration

Details of using fast enumeration on a copy of an NSMutableArray to remove objects


I learnt the hard way that you can't remove objects from an NSMutableArray when you are looping through the objects in it.

Looping through [< array_object > copy] instead of < array_object > fixes it.

However, I have a few unanswered questions that I would like input on from the Objective-C gurus.

  1. In this first for loop, I expected each of the nextObjects to point to different memory (i.e I thought the msgDetail array will have a list of pointers, each pointing to the address of the NSDictionary that a particular array index contains). But all of the %p nextObject prints are giving the same value. Why is that?

    for(NSDictionary *nextObject in msgDetailArray)
    {
    
        NSLog(@"Address = %p, value = %@",&nextObject,nextObject) ;
        //Doing [msgDetailArray removeObject:nextObject] here based on some condition fails
    }
    
  2. In the second for loop, the address of the nextObject NSDictionarys are different from that printed in the first for loop.

    for(id nextObject in [msgDetailArray copy])
    {
    
        NSLog(@"In copy Address = %p, value = %@",&nextObject,nextObject) ;
    
        //Doing [msgDetailArray removeObject:nextObject] here based on some condition succeeds and it also removes it from the original array even though I am looping through a copy
    }
    
  3. However, when I loop through [msgDetailArray copy], and then do a removeObject:, it removes it from the original msgDetailArray. How does removeObject: do this? Does it actually use the contents of the dictionary and remove an object that matches the content? I thought all it does is to check if there is an object that is in the same memory location and remove it (Based on 2, I assume the memory address containing the dictionaries in [msgDetailArray copy] are not the same as the addresses in the original msgDetailArray). If it is actually using contents, I will have to be very careful in case there are duplicate entries.

    for(id nextObject in msgDetailArray)
    {
            NSLog(@"Address = %p, value = %@",&nextObject,nextObject) ;
            //test what is left in msgDetailArray.  I see that doing removeObject on [msgDetailArray copy] does remove it from the original too.  How is removeObject working (is it actually contents of dictionary)
    }
    

Solution

  • Copying an array does a "shallow copy". It creates a new array object, then stores pointers to the objects in the first array. It does not create new objects.

    Think of an array like an address book. It lists the addresses of your friends' houses. If I make a copy of your address book, then I have a copy of the list of the addresses. The address books do not contain houses.

    If I erase an entry from my copy of the address book, it does not erase the same entry from your address book. Nor does it destroy any houses.

    In your loop 2 code, you are looping through the copy, then telling your original array to delete certain objects. They are deleted from the original array, but not from the copy. (Which is good, because as you say, mutating an array as you iterate through it causes a crash.)

    Note that arrays can contain more than one pointer to the same object, just like addresses can contain the same address more than once. (If a friend changes her name when she gets married, you might write her into your address under he married name and leave the entry under her maiden name as well. Both addresses point to the same house.)

    Note that the removeObject method that you are using removes ALL entries for an object. It's like you're saying "erase every entry in my address book for 123 Elm street". If you only want to remove one entry of a duplicate set then you need to use removeObjectAtIndex instead, and you would need to change your code to make that work.