Reputation: 811
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.
In this first for loop, I expected each of the nextObject
s 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
}
In the second for loop, the address of the nextObject
NSDictionary
s 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
}
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)
}
Upvotes: 4
Views: 218
Reputation: 726899
But all of the
%p
s prints are giving the same value. Why is that?
Because &nextObject
is the address of the nextObject
variable, a loop variable used in your code example; it's type is a pointer to a pointer. nextObject
itself is a pointer, too, so if you would like to print the address of the object, all you need to do is casting it to void*
:
NSLog(@"Address = %p, value = %@", (void*)nextObject, nextObject);
In the second for loop, the address of the
nextObject
NSDictionaries
are different from that printed in the first for loop
Because that's the address of a different variable.
when I loop through
[msgDetailArray copy]
, and then do aremoveObject
, it removes it from the originalmsgDetailArray
.
The copy through which you are looping is in an invisible temporary object. It is an immutable copy backed by the original array.
The call [msgDetailArray removeObject:nextObject]
operates on the original array. If you do not wish this effect, store the copy in a variable, and delete items from it instead:
NSMutableArray *mutableCopy = [msgDetailArray mutableCopy];
...
[mutableCopy removeObject:nextObject];
This will not touch the original array. However, you may not iterate mutableCopy
at the same time as you delete items from it.
Upvotes: 2
Reputation: 64012
The copy operation copies the container, not its contents. You have a new list of pointers to the same objects.
&nextObject
is the address of the variable, not the object. The address of the object is the value of the variable: NSLog(@"%p", nextObject);
removeObject:
compares its argument with the contents of the collection using isEqual:
, which generally does not compare by address, but by some kind of semantic valuation defined by the class.
Even leaving aside the prohibition on mutating inside a fast enumeration loop, you wouldn't be able to remove objects from the copy, because copy
makes an immutable copy. You need mutableCopy
in order to be able to change the new instance.
Upvotes: 2
Reputation: 131481
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.
Upvotes: 2