how to do true deep copy for NSArray and NSDictionary with have nested arrays/dictionary?

19,193

Solution 1

A couple of years ago, I wrote a few category methods for exactly the same reason, transforming a whole tree of user defaults to mutable. Here they are - use them at your own risk! :-)

//
//  SPDeepCopy.h
//
//  Created by Sherm Pendley on 3/15/09.
//

#import <Cocoa/Cocoa.h>

// Deep -copy and -mutableCopy methods for NSArray and NSDictionary

@interface NSArray (SPDeepCopy)

- (NSArray*) deepCopy;
- (NSMutableArray*) mutableDeepCopy;

@end

@interface NSDictionary (SPDeepCopy)

- (NSDictionary*) deepCopy;
- (NSMutableDictionary*) mutableDeepCopy;

@end

//
//  SPDeepCopy.m
//
//  Created by Sherm Pendley on 3/15/09.
//

#import "SPDeepCopy.h"


@implementation NSArray (SPDeepCopy)

- (NSArray*) deepCopy {
    unsigned int count = [self count];
    id cArray[count];

    for (unsigned int i = 0; i < count; ++i) {
        id obj = [self objectAtIndex:i];
        if ([obj respondsToSelector:@selector(deepCopy)])
            cArray[i] = [obj deepCopy];
        else
            cArray[i] = [obj copy];
    }

    NSArray *ret = [[NSArray arrayWithObjects:cArray count:count] retain];

    // The newly-created array retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i)
        [cArray[i] release];

    return ret;
}
- (NSMutableArray*) mutableDeepCopy {
    unsigned int count = [self count];
    id cArray[count];

    for (unsigned int i = 0; i < count; ++i) {
        id obj = [self objectAtIndex:i];

        // Try to do a deep mutable copy, if this object supports it
        if ([obj respondsToSelector:@selector(mutableDeepCopy)])
            cArray[i] = [obj mutableDeepCopy];

        // Then try a shallow mutable copy, if the object supports that
        else if ([obj respondsToSelector:@selector(mutableCopyWithZone:)])
            cArray[i] = [obj mutableCopy];

        // Next try to do a deep copy
        else if ([obj respondsToSelector:@selector(deepCopy)])
            cArray[i] = [obj deepCopy];

        // If all else fails, fall back to an ordinary copy
        else
            cArray[i] = [obj copy];
    }

    NSMutableArray *ret = [[NSMutableArray arrayWithObjects:cArray count:count] retain];

    // The newly-created array retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i)
        [cArray[i] release];

    return ret;
}

@end

@implementation NSDictionary (SPDeepCopy)

- (NSDictionary*) deepCopy {
    unsigned int count = [self count];
    id cObjects[count];
    id cKeys[count];

    NSEnumerator *e = [self keyEnumerator];
    unsigned int i = 0;
    id thisKey;
    while ((thisKey = [e nextObject]) != nil) {
        id obj = [self objectForKey:thisKey];

        if ([obj respondsToSelector:@selector(deepCopy)])
            cObjects[i] = [obj deepCopy];
        else
            cObjects[i] = [obj copy];

        if ([thisKey respondsToSelector:@selector(deepCopy)])
            cKeys[i] = [thisKey deepCopy];
        else
            cKeys[i] = [thisKey copy];

        ++i;
    }

    NSDictionary *ret = [[NSDictionary dictionaryWithObjects:cObjects forKeys:cKeys count:count] retain];

    // The newly-created dictionary retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i) {
        [cObjects[i] release];
        [cKeys[i] release];
    }

    return ret;
}
- (NSMutableDictionary*) mutableDeepCopy {
    unsigned int count = [self count];
    id cObjects[count];
    id cKeys[count];

    NSEnumerator *e = [self keyEnumerator];
    unsigned int i = 0;
    id thisKey;
    while ((thisKey = [e nextObject]) != nil) {
        id obj = [self objectForKey:thisKey];

        // Try to do a deep mutable copy, if this object supports it
        if ([obj respondsToSelector:@selector(mutableDeepCopy)])
            cObjects[i] = [obj mutableDeepCopy];

        // Then try a shallow mutable copy, if the object supports that
        else if ([obj respondsToSelector:@selector(mutableCopyWithZone:)])
            cObjects[i] = [obj mutableCopy];

        // Next try to do a deep copy
        else if ([obj respondsToSelector:@selector(deepCopy)])
            cObjects[i] = [obj deepCopy];

        // If all else fails, fall back to an ordinary copy
        else
            cObjects[i] = [obj copy];

        // I don't think mutable keys make much sense, so just do an ordinary copy
        if ([thisKey respondsToSelector:@selector(deepCopy)])
            cKeys[i] = [thisKey deepCopy];
        else
            cKeys[i] = [thisKey copy];

        ++i;
    }

    NSMutableDictionary *ret = [[NSMutableDictionary dictionaryWithObjects:cObjects forKeys:cKeys count:count] retain];

    // The newly-created dictionary retained these, so now we need to balance the above copies
    for (unsigned int i = 0; i < count; ++i) {
        [cObjects[i] release];
        [cKeys[i] release];
    }

    return ret;
}

@end

Solution 2

I think something like this should work.

NSData *buffer;
NSMutableDictionary *_dict1, *_dict2;

// Deep copy "all" objects in _dict1 pointers and all to _dict2  
buffer = [NSKeyedArchiver archivedDataWithRootObject: _dict1];  
_dict2 = [NSKeyedUnarchiver unarchiveObjectWithData: buffer];  

Solution 3

The easy way for a DEEP copy is to simply use CFPropertyListCreateDeepCopy. Here is example (with ARC):

    NSDictionary *newDictionary =
 (__bridge NSDictionary *)(CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
         (__bridge CFPropertyListRef)(originalDictionary),
          kCFPropertyListImmutable));

my originalDictionary in this example is an NSDictionary

CFPropertyListRef can simply be cast to NSDictionary as long as the top level entity of the CFPropertyListRef is a CFDictionary.

Solution 4

If someone want the mutable copy then please use below code:

NSMutableDictionary *newDictionary = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)dict, kCFPropertyListMutableContainers));

Share:
19,193
Greg
Author by

Greg

Updated on June 13, 2022

Comments

  • Greg
    Greg almost 2 years

    Question: Is there a way to use existing objective-c methods to do a full deep copy of a NSDictionary or NSArray, that themselves have nested dictionaries or arrays within them?

    That is I have read the problem may be when it hits a nested dictionary or array it only copies the pointer to the nested item, and not copy the item truely.

    Background: So as an example for me I'm trying to load/save the following config with NSUserDefaults and when loading need to convert the immutable copies one gets from NSUserDefault to mutable prior to making changes.

    • Items (NSDictionary)
      • Item (NSDictionary)
        • aString: NSString
        • aString2: NSString
        • aDate: NSDate
        • aDate2: NSDate
        • aBool: BOOL
        • aTI1: NSTimeInterval
        • aTI2: NSTimeInterval
        • Keywords (NSArray)
          • keyword: NSString
          • keyword: NSString
  • Greg
    Greg about 13 years
    excellent Sherm thanks - so there's nothing that Apple has that does this for you obviously I guess then? one would wonder if this was the case whether there might be some popular open source library that captures such a useful method?
  • Sherm Pendley
    Sherm Pendley about 13 years
    No, Apple doesn't supply anything like that. There may be an FOSS library that includes it, but to be honest I didn't look very hard, since this took all of an hour to write anyway.
  • Morrowless
    Morrowless almost 13 years
    Thanks for this. Works great!
  • uvesten
    uvesten almost 13 years
    Just a note, for iOS, the #import statement in the .h file needs to be #import <Foundation/Foundation.h>
  • Saturn
    Saturn almost 12 years
    If I use mutableDeepCopy for a dictionary, do I have to release it myself? Also, does it work with very deep dictionaries? (Like a dictionary within an array within another dictionary within yet another dictionary... etc...)
  • Mazyod
    Mazyod over 10 years
    Genius.. Especially since I already have the encoder/decoder methods implemented for all the custom objects.
  • Alex Cio
    Alex Cio over 9 years
    This didn't work for me. I also found some other code snippets which looked nearly the same just switched with the CFProperties which are available. Looks to me like you have to be careful on different things when using this
  • Reuben Scratton
    Reuben Scratton about 9 years
    Yes, this is genius. :)
  • Sean Dev
    Sean Dev over 8 years
    @Voldermord Find SPDeepCopy.m in Compile Sources in the Build Phases section of your Project Target and add "-fno-objc-arc" as the compiler tags and then clean and build. You can copy his code verbatim this way.
  • robm
    robm over 8 years
    see the ARC solution here: stackoverflow.com/questions/1950361/…
  • sabajt
    sabajt over 7 years
    Apple even recommends this method in official docs: developer.apple.com/library/content/documentation/Cocoa/…
  • Motti Shneor
    Motti Shneor over 2 years
    Actually Apple DOES provide such deep copies, only you need to go to the underlying CoreFoundation APIs for that. see CFPropertyListCreateDeepCopy() and friends.
  • Motti Shneor
    Motti Shneor over 2 years
    This is very nice and useful to know about, but pay attention this only works on PropertyList hierarchies - that are very limited in the types of objects stored, and won't accept any custom classes at all.