BuySellAds.com

My book Barcodes with iOS is nearing completion. Buy it now to get early access!
Our DNA is written in Objective-C
Jump

Deep-Copying Dictionaries

Cocoa Touch has this concept of basic functionality being immutable and then there is a bigger brother of most classes that adds mutability. One effect of this pradadigm is that often you reuse a single NSString throughout your code because you pass around a pointer to it, retaining it wherever you are interested in it’s continued existence in memory.

Only very rarely you would see the use of the [receiver copy] approach which is like an init but creates a copy of the object. As I said this rarely makes sense because if the original is non-mutable in the first place why do I need to clone it? If it is used in another place and then there it is released and some other static value retained it does not have any adverse effect on other places where it is used, provided that proper retaining occurs.

But this feature is there because there might really BE some esotheric cases. There is even the “copy” attribute for properties which sets up your setters to copy the incoming objects, as opposed to the “retain” attribute which simply retains it.

@property (nonatomic, retain) NSString *myString;
@property (nonatomic, copy) NSMutableString *myOtherString;
@property (nonatomic, assign) BOOL isCool;

For retain and copy you shall not forget to add a release in the dealloc method. Otherwise this will leak if the containing object is deallocated from memory. But never release something that was only assigned because then this will cause over-releasing at a different place and can cause weird exceptions outside of your code, making it that much harder to track.

- (void)dealloc
{
	[myString release];
	[myOtherString release];
	[super dealloc];
}

Having said that there is a more complex scenario when dealing with container objects. Those are NSArray and NSDictionary primarily, and their mutable descendants NSMutableArray and NSMutableDictionary. For a recent project I needed to make a copy of a mutable dictionary and found that it actually only copies the pointers but does not copy the objects being referenced.

What I needed was called a “deep copy” essentially creating a complete copy of the whole tree contained in the dictionary. Not just the pointers which would continue to point to the same objects, but also clone the objects and their sub-objects and so on and so forth.

The fastest way I found via Google was a recommendation to use NSArchiver and NSUnarchiver to archive and then unarchive the object in one line. Unfortunately those are not available on the Cocoa Touch Framework, but instead there are NSKeyedArchiver and NSKeyedUnarchiver. With a bit of docu-digging I came up with this approach which appears to be doing exactly what I need:

// theTreeDict is a mutable dictionary
NSMutableDictionary *deepCopy = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: theTreeDict]];

The inner part packs the dictionary to be copied into an NSData, the outer part unpacks it right away creating a totally new copy. Most likely this approach will only work with standard contents like NSString, NSData, NSDate, NSArray, NSNumber etc. To be able to encode/decode your custom classes you will need to provide the initWithCoder and encodeWithCoder methods.

Of course this lends itself to creating a category for NSMutableDictionary and NSDictionary to provide such functionality with less writing. Now creating such a copyDeep extension method raises an interesting question: would we return an autoreleased object or one with retain count 1? To maximize utility and reduce necessary code I would probably opt for the former. But while I was researching this I stumbled on the official method of deep-copying, again buried in CoreFoundation.

- (NSMutableDictionary *)dictionaryByDeepCopying
{
	CFPropertyListRef plist = CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
							self, kCFPropertyListMutableContainersAndLeaves);
 
	// we check if it is the correct type and only return it if it is
	if ([(id)plist isKindOfClass:[NSMutableDictionary class]])
	{
		return [(NSMutableDictionary *)plist autorelease];
	}
	else
	{
		// clean up ref
		CFRelease(plist);
		return nil;
	}
}

So you either use the Cocoa high level approach or you use the richer CoreFoundation method. In both cases you end up with an autoreleased new deep copy of the dictionary. Now changes to “leaves” don’t modify the original.

If you find a scenario where it makes sense to copy dictionaries all the time, let me know.


Categories: Recipes

%d bloggers like this: