deep mutable copy of a NSMutableDictionary
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 rel开发者_运维百科ease];
}
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).
Because of toll-free bridging, you can also use the CoreFoundation function CFPropertyListCreateDeepCopy
:
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
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.
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.
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:
- Tom Dalling's answer
- dreamlax's answer
- Source from yfujiki on GitHub Gist
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;
}
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.
For ARC - note kCFPropertyListMutableContainersAndLeaves for truly deep mutability.
NSMutableDictionary* mutableDict = (NSMutableDictionary *)
CFBridgingRelease(
CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
(CFDictionaryRef)someNSDict,
kCFPropertyListMutableContainersAndLeaves));
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));
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.
精彩评论