views:

792

answers:

4

I am trying to create a deep-copy of a NSMutableDictionary and assign it to another NSMutableDictionary. The dictionary contains a bunch of arrays, each array containing names, and the key is an alphabet (the first letter of those names). So one entry in the dictionary is 'A' -> 'Adam', 'Apple'. Here's what I saw in a book, but I'm not sure if it works:

- (NSMutableDictionary *) mutableDeepCopy
{
    NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity: [self count]];
    NSArray *keys = [self allKeys];

    for (id key in keys)
    {
     id oneValue = [self valueForKey:key]; // should return the array
     id oneCopy = nil;

        if ([oneValue respondsToSelector: @selector(mutableDeepCopy)])
        {
            oneCopy = [oneValue mutableDeepCopy];
        }
     if ([oneValue respondsToSelector:@selector(mutableCopy)])
     {
      oneCopy = [oneValue mutableCopy];
     }

     if (oneCopy == nil) // not sure if this is needed
     { 
      oneCopy = [oneValue copy];
     }
     [ret setValue:oneCopy forKey:key];

     //[oneCopy release];
    }
    return ret;
}
  • should the [onecopy release] be there or not?
  • Here's how I'm going to call this method:

    self.namesForAlphabets = [self.allNames mutableDeepCopy];

Will that be ok? Or will it cause a leak? (assume that I declare self.namesForAlphabets as a property, and release it in dealloc).

+1  A: 

That code should work, but you will definitely need the [oneCopy release]. The new dictionary will retain the copied objects when you add them with setValue:forKey, so if you do not call [oneCopy release], all of those objects will be retained twice.

A good rule of thumb: if you alloc, retain or copy something, you must also release it.

edit: Since you are only dealing with arrays, you could do:

- (NSMutableDictionary *)mutableDeepCopy
{
    NSMutableDictionary * ret = [[NSMutableDictionary alloc]
                                  initWithCapacity:[self count]];

    NSMutableArray * array;

    for (id key in [self allKeys])
    {
        array = [(NSArray *)[self objectForKey:key] mutableCopy];
        [ret setValue:copy forKey:key];
        [array release];
    }

    return ret;
}
e.James
Thanks, but the "copy" makes the NSArray (or NSMutableArray) immutable in the new dictionary. So that's not going to work.
z s
replaced 'copy' with 'mutableCopy' and it's fine.
z s
Ah, yes. Sorry! That should definitely have been `mutableCopy`. I made the change in my answer.
e.James
+1  A: 
dreamlax
+1  A: 

Assuming all elements of the array implement the NSCoding protocol, you can do deep copies via archiving because archiving will preserve the mutability of objects.

Something like this:

id DeepCopyViaArchiving(id<NSCoding> anObject)
{
    NSData* archivedData = [NSKeyedArchiver archivedDataWithRootObject:anObject];
    return [[NSKeyedUnarchiver unarchiveObjectWithData:archivedData] retain];
}

This isn't particularly efficient, though.

Tom Dalling
Does this method return a mutable deep copy or an immutable deep copy?
dreamlax
Whatever you put into it. If you put in an NSMutableArray, you get back an NSMutableArray.
Tom Dalling
+4  A: 

Because of toll-free bridging, you can also use the CoreFoundation function CFPropertyListCreateDeepCopy:

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionary)originalDictionary, kCFPropertyListMutableContainers);
Wevah
Cool. This works great. Except, for some reason it registers as a leak in performance tool. Any idea what that's about?
Jonah
It follows Core Foundation's "create" rule, so you need to make sure you release or autorelease the returned dictionary (or just not retain it if you want to keep it around).
Wevah
So it seems you'd have to call: CFRelease(mutableCopy); when you need to release the object. Thanks!
Jonah