39

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
  • There is nothing "deep" in the structure you describe (a simple address-book style list of names by their first letter) -- so why you even attempt a "deep copy" ? what do you need this for? Commented Jan 27, 2022 at 10:15

8 Answers 8

77

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

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
10
  • Cool. This works great. Except, for some reason it registers as a leak in performance tool. Any idea what that's about?
    – Jonah
    Commented Aug 25, 2010 at 13:54
  • 2
    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
    Commented Aug 25, 2010 at 16:01
  • 13
    this only works if there are only property-list-values though. if it encounters anything else it just returns NULL
    – Ahti
    Commented Nov 30, 2011 at 15:32
  • 4
    I think you have to use CFBridgeRelease. For an array it is, mutableArray = (NSMutableArray *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFArrayRef)oldArrat, kCFPropertyListMutableContainers)); Commented Aug 29, 2014 at 9:13
  • 1
    It only works if all the elements are property list objects (strings, arrays, dictionaries, numbers, dates, data). Do you have any other objects in your dictionary?
    – Wevah
    Commented Feb 11, 2015 at 17:54
13

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.

7
  • Does this method return a mutable deep copy or an immutable deep copy?
    – dreamlax
    Commented Dec 23, 2009 at 3:04
  • 3
    Whatever you put into it. If you put in an NSMutableArray, you get back an NSMutableArray. Commented Dec 23, 2009 at 4:28
  • @dreamlax if not, you could just create a mutable version of it with mutableCopy
    – Alex Cio
    Commented Feb 10, 2015 at 14:57
  • 1
    @亚历山大: That would just create a mutable "top-level" but any nested container objects would still be immutable.
    – dreamlax
    Commented Feb 12, 2015 at 2:35
  • Ok, sorry your right. In my case, I didn't have any nested containers which needed to be mutable.
    – Alex Cio
    Commented Feb 16, 2015 at 11:42
10

IMPORTANT: The question (and my code below) both deal with a very specific case, in which the NSMutableDictionary contains only arrays of strings. These solutions will not work for more complex examples. For more general case solutions, see the following:


Answer for this specific case:

Your 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.

Note: here is some sample code that would work for certain cases only. This works because your NSMutableDictionary contains only arrays of strings (no further deep copying required):

- (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:array forKey:key];
        [array release];
    }

    return ret;
}
7
  • 1
    Thanks, but the "copy" makes the NSArray (or NSMutableArray) immutable in the new dictionary. So that's not going to work.
    – Z S
    Commented Dec 23, 2009 at 4:54
  • 3
    replaced 'copy' with 'mutableCopy' and it's fine.
    – Z S
    Commented Dec 23, 2009 at 5:25
  • Ah, yes. Sorry! That should definitely have been mutableCopy. I made the change in my answer. Commented Dec 23, 2009 at 14:59
  • 1
    There is also a typo in the middle of for loop - replace setValue:copy with setValue:array
    – Nik
    Commented May 9, 2011 at 22:44
  • 1
    @Krishnabhadra: Yes, you will definitely need to implement NSCopying if you are using custom objects. The original question dealt only with NSArray objects. Commented Jul 19, 2011 at 16:33
10

Another technique that I have seen (which is not at all very efficient) is to use an NSPropertyListSerialization object to serialise your dictionary, then you de-serialise it but specify that you want mutable leaves and containers.


NSString *errorString = nil;
NSData *binData = 
  [NSPropertyListSerialization dataFromPropertyList:self.allNames
                                             format:NSPropertyListBinaryFormat_v1_0
                                        errorString:&errorString];

if (errorString) {
    // Something bad happened
    [errorString release];
}

self.namesForAlphabets = 
 [NSPropertyListSerialization propertyListFromData:binData
                                  mutabilityOption:NSPropertyListMutableContainersAndLeaves
                                            format:NULL
                                  errorDescription:&errorString];

if (errorString) {
    // something bad happened
    [errorString release];
}

Again, this is not at all efficient.

2
  • 2
    why is this method inefficient? Commented Sep 23, 2013 at 23:19
  • because serialization and de-serialization into PLIST are slow memory-consuming processes, while the other techniques generally play with pointers to objects, not duplicating their contents. Commented Aug 21, 2019 at 18:29
5

Trying to figure out by checking respondToSelector(@selector(mutableCopy)) won't give the desired results as all NSObject-based objects respond to this selector (it's part of NSObject). Instead we have to query if an object conforms to NSMutableCopying or at least NSCopying. Here's my answer based on this gist mentioned in the accepted answer:

For NSDictionary:

@implementation NSDictionary (MutableDeepCopy)

//  As seen here (in the comments): https://gist.github.com/yfujiki/1664847
- (NSMutableDictionary *)mutableDeepCopy
{
    NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count];

    NSArray *keys = [self allKeys];

    for(id key in keys) {
        id oneValue = [self objectForKey:key];
        id oneCopy = nil;

        if([oneValue respondsToSelector:@selector(mutableDeepCopy)]) {
            oneCopy = [oneValue mutableDeepCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) {
            oneCopy = [oneValue mutableCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){
            oneCopy = [oneValue copy];
        } else {
            oneCopy = oneValue;
        }

        [returnDict setValue:oneCopy forKey:key];
    }

    return returnDict;
}

@end

For NSArray:

@implementation NSArray (MutableDeepCopy)

- (NSMutableArray *)mutableDeepCopy
{
    NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count];

    for(id oneValue in self) {
        id oneCopy = nil;

        if([oneValue respondsToSelector:@selector(mutableDeepCopy)]) {
            oneCopy = [oneValue mutableDeepCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) {
            oneCopy = [oneValue mutableCopy];
        } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){
            oneCopy = [oneValue copy];
        } else {
            oneCopy = oneValue;
        }

        [returnArray addObject:oneCopy];
    }

    return returnArray;
}

@end

Both methods have the same internal to-copy-or-not-to-copy logic and that could be extracted into a separate method but I left it like this for clarity.

1
  • I thought this beautiful, Tried to use the technique, and it seemed to work... until it didn't. I need to use KVC 'setValue:forKeyPath:' to modify lower-level items in a dictionary retrieved from '[NSUserDefaults standardDefaults]' - which always returns immutable objects (all the way down their hierarchy). The technique failed, because the NSDictionaries/NSArrays retrieved - aren't really NSDictionaries. They are bridged _NSCFDictionary objects - which don't conform to either your 'mutableDeepCopy' protocol, nor to 'NSMutableCopying' protocol. So the code breaks. I'm still looking for a cure. Commented Aug 21, 2019 at 18:46
2

For ARC - note kCFPropertyListMutableContainersAndLeaves for truly deep mutability.

    NSMutableDictionary* mutableDict = (NSMutableDictionary *)
      CFBridgingRelease(
          CFPropertyListCreateDeepCopy(kCFAllocatorDefault, 
           (CFDictionaryRef)someNSDict, 
           kCFPropertyListMutableContainersAndLeaves));
0
1

Thought I'd update with an answer if you're using ARC.

The solution Weva has provided works just fine. Nowadays you could do it like this:

NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDict, kCFPropertyListMutableContainers));
1
  • 2
    kCFPropertyListMutableContainersAndLeaves is maybe also what you want, which makes the mutability deeper. Commented Sep 23, 2015 at 21:36
0

Useful answers here, but CFPropertyListCreateDeepCopy doesn't handle [NSNull null] in the data, which is pretty normal with JSON decoded data, for example.

I'm using this category:

    #import <Foundation/Foundation.h>

    @interface NSObject (ATMutableDeepCopy)
    - (id)mutableDeepCopy;
    @end

Implementation (feel free to alter / extend):

    @implementation NSObject (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        return [self copy];
    }

    @end

    #pragma mark - NSDictionary

    @implementation NSDictionary (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        return [NSMutableDictionary dictionaryWithObjects:self.allValues.mutableDeepCopy
                                                  forKeys:self.allKeys.mutableDeepCopy];
    }

    @end

    #pragma mark - NSArray

    @implementation NSArray (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        NSMutableArray *const mutableDeepCopy = [NSMutableArray new];
        for (id object in self) {
            [mutableDeepCopy addObject:[object mutableDeepCopy]];
        }

        return mutableDeepCopy;
    }

    @end

    #pragma mark - NSNull

    @implementation NSNull (ATMutableDeepCopy)

    - (id)mutableDeepCopy
    {
        return self;
    }

    @end

Example extensions – strings are left as normal copies. You could override this if you want to be able to in place edit them. I only needed to monkey with a deep down dictionary for some testing, so I've not implemented that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.