views:

1520

answers:

4

I am creating an array of dictionaries in a class. I want to return a copy of that array to any other object that asks for it. This copy that is passed to other objects needs to be modified without modifying the original.

So I am using the following in a getter method of my class that holds the "master" array:

[[NSMutableArray alloc] initWithArray:masterArray copyItems:YES];

However, this seems to make all the dictionaries inside immutable. How can I avoid this?

I think I am missing something here. Any help will be much appreciated!

+4  A: 

Copying an array will create a copy of the array object, with references to the original content objects (appropriately retained.)

Per the documentation, -initWithArray:copyItems: initializes the new array with copies of the items in the original array. This copy is created by sending the original content objects -copyWithZone:, which will create an immutable copy in the case of mutable objects.

If you need different behavior (i.e. mutable copies of the content objects, or deep copies of the content objects) you'll have to write your own convenience function/method to do so.

Jim Correia
that was a really inane edit quinn. it's perfectly acceptable to offset that phrase with commas as in the original. if you're gonna be a grammar nazi, you still need a comma after the "i.e.", even inside the parentheses.
Jason Coco
Not to mention one after the parenthesis. But yeah, every edit is a step towards community-wiki'ing someone's post (http://stackoverflow.com/questions/128434/what-are-community-wiki-posts-on-stack-overflow/128436#128436 ), so let's not waste them on “fixing” others' grammar, with the sole exception of digitally restoring an otherwise-unreadable post.
Peter Hosey
+2  A: 

Jim is correct about -initWithArray:copyItems sending a -copyWithZone: message to each element. To get mutable copies of array elements, you'd need to send -mutableCopyWithZone: (or just -mutableCopy for brevity) to each element. This is fairly straightforward:

NSMutableArray *masterArray = ...
NSMutableArray *clone = [NSMutableArray arrayWithCapacity:[masterArray count]];
for (id anObject in masterArray)
    [clone addObject:[anObject mutableCopy]]; // OR [clone addObject:anObject];

However, there is a deeper question hidden in your explanation of the problem: it seems you want both the array and its elements (dictionaries) to be mutable, but there are a few fine points that should be cleared up, especially in light of your stipulation that "[the] copy that is passed to other objects needs to be modified without modifying the original." Depending on exactly what you mean, this can be quite complex to guarantee and implement.

For example, suppose that the original array contains a number of mutable dictionaries. Creating mutable copies of these first two levels means that someone who obtains a copy can modify their own array and dictionaries in the array without changing the original array or dictionary directly. However, if a dictionary contains mutable objects (like an NSMutableArray, NSMutableString, etc.), the code using the "copy" could modify the contents of the dictionary indirectly, using only a reference to the mutable copy.

Mutable copies are "shallow", meaning only the first level is copied, and the copy has pointers to the same elements as the original structure. Thus, guaranteeing that there is no linkage between the original and the copy (at least, manually) would require a sweep through the entire structure and making copies. This can become more complicated if some elements don't conform to NSCopying or NSMutableCopying.

The easiest and fastest solution is to use the simple code above or just return a reference to the master array. This requires trusting that the client code will not modify the array, so this may not work in all situations, but if you control the calling code as well, this may be preferable. On the other hand, if you definitely want a totally separate copy, consider making use of NSCoding / keyed archiving:

NSMutableArray *masterArray = ...
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:masterArray];
NSMutableArray *clone = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Basically, this converts everything into raw data bytes, then reconstitutes it into a new set of objects. For this to work, all the objects in the dictionary must conform to the NSCoding protocol. This can take a bit of doing, but it's an elegant generic way to guarantee object uniqueness. This certainly has a non-zero performance cost, but if you absolutely must guarantee there will be no side effects, it should work.

Quinn Taylor
+4  A: 

One way would be to abuse Key-Value Coding to send mutableCopy to each of the dictionaries and autorelease to each of the copies. But that's a dirty, dirty hack, so don't do that. Indeed, you probably shouldn't be doing this in the first place.

Generally, when I see the words “array of dictionaries”, I get the idea that you're using dictionaries as substitutes for model objects. Don't do that. Write your own model classes; everything becomes much easier when you have your own custom properties and behavior methods to work with. (Some things more than others: Implementing AppleScript support is virtually impossible without a proper model layer.)

And once you have real model objects, you can implement NSCopying in them and not need to worry about mutable versus immutable, since you probably won't have a mutability distinction in your real model classes anyway. (I don't know about others, but I've never made such a distinction in my model classes.) Then you can just use the existing NSArray initWithArray:copyItems: method.

Peter Hosey
Ew ew ew, please don't even mention things like using -valueForKey: this way where newbies can see and be misled by it. (They will, you know: "On the Internet I read the way to do mutable copies was..." is how it will be remembered.)
Chris Hanson
I've changed that paragraph to be less specific and to more strongly recommend against it. What do you think now?
Peter Hosey
Much better, thanks!
Chris Hanson
+5  A: 

Another approach you could take is to use the CFPropertyListCreateDeepCopy() function (in the CoreFoundation framework), passing in kCFPropertyListMutableContainers for the mutabilityOption argument. The code would look like:

NSMutableArray* originalArray;
NSMutableArray* newArray;

newArray = (NSMutableArray*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFPropertyListRef)originalArray, kCFPropertyListMutableContainers);

This will not only create mutable copies of the dictionaries, but it would also make mutable copies of anything contained by those dictionaries recursively. Do note though that this will only work if your array of dictionaries only contains objects that are valid property lists (array, number, date, data, string, and dictionary), so this may or may not be applicable in your particular situation.

Brian Webster
Any idea why performance tool would show this as a leak?
Jonah
Just like any other Core Foundation function that does a "create" or "copy", you are responsible for releasing the returned value once you are done with it.
Brian Webster