how to do true deep copy for NSArray and NSDictionary with have nested arrays/dictionary?
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));
Greg
Updated on June 13, 2022Comments
-
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
- Item (NSDictionary)
- Items (NSDictionary)
-
Greg about 13 yearsexcellent 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 about 13 yearsNo, 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 almost 13 yearsThanks for this. Works great!
-
uvesten almost 13 yearsJust a note, for iOS, the
#import
statement in the .h file needs to be#import <Foundation/Foundation.h>
-
Saturn almost 12 yearsIf 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 over 10 yearsGenius.. Especially since I already have the encoder/decoder methods implemented for all the custom objects.
-
Alex Cio over 9 yearsThis 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 about 9 yearsYes, this is genius. :)
-
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 over 8 yearssee the ARC solution here: stackoverflow.com/questions/1950361/…
-
sabajt over 7 yearsApple even recommends this method in official docs: developer.apple.com/library/content/documentation/Cocoa/…
-
Motti Shneor over 2 yearsActually Apple DOES provide such deep copies, only you need to go to the underlying CoreFoundation APIs for that. see CFPropertyListCreateDeepCopy() and friends.
-
Motti Shneor over 2 yearsThis 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.