(Almost) every time you use retain
in Objective-C/Cocoa, you really should be using copy
. Using retain
can introduce some subtle bugs, and copy
is faster then you think…
A Bug Waiting To Bite
The problem with using retain
to “take ownership” of an object is that someone else has a pointer to the same object, and if they change it, you will be affected.
For example, let’s say you have a Person
class with a straightforward setter method:
- (void) setThingsToCallTheBossToHisFace:(NSArray*)newNames {
[thingsToCallTheBossToHisFace autorelease];
thingsToCallTheBossToHisFace = [newNames retain];
}
And you use it to initialize a few Person
objects:
NSMutableArray *appropriateNames = [NSMutableArray arrayWithObject:@"Mr. Smith"];
[anIntern setThingsToCallTheBossToHisFace:appropriateNames];
//Salaried Employees can also be a bit more informal
[appropriateNames addObject:@"Joe"];
[aSalariedEmployee setThingsToCallTheBossToHisFace:appropriateNames];
//the wife can also use terms of endearment
[appropriateNames addObject:@"Honey"];
[appropriateNames addObject:@"Darling"];
[theBossesWife setThingsToCallTheBossToHisFace:appropriateNames];
The code looks good, it compiles without error, and it has a bug in it. Because setThingsToCallTheBossToHisFace:
uses retain
, each Person
object’s thingsToCallTheBossToHisFace
field is actually pointing to the exact same NSMutableArray
. So adding “darling” to the list of names the wife can use also adds it to the intern’s vocabulary.
If copy
was used instead, then each Person
would have their own separate list of names, insulated from changes to the temporary variable appropriateNames
.
A Sneaky Bug Too
This is a particularly insidious problem in Foundation/Cocoa, because mutable objects are subclasses of immutable objects. This means every NSMutableThing
is also a NSThing
. So even if a method is declared to take an immutable object, if someone passes in a mutable object by accident, there will be no compile-time or run-time warnings.
Unfortunately, there isn’t a good way to enforce that a method takes an object, but not a subclass. Because Foundation makes heavy use of class clusters, it’s very difficult to figure out if you have an immutable class, or it’s mutable subclass. For example, with:
NSArray *immutableArray = [NSArray array];
NSMutableArray *mutableArray = [NSMutableArray array];
[immutableArray isKindOfClass:[NSArray class]] is YES
[immutableArray isKindOfClass:[NSMutableArray class]] is YES
[mutableArray isKindOfClass:[NSArray class]] is YES
[mutableArray isKindOfClass:[NSMutableArray class]] is YES
[mutableArray isKindOfClass:[immutableArray class]] is YES
[immutableArray isKindOfClass:[mutableArray class]] is YES
Sad, but true
.
copy
Is Fast!
With nearly every immutable Foundation object, copy
and retain
are the same thing — there is absolutely no penalty for using copy
over retain
! The only time you would take a performance hit using copy
would be if the object actually was mutable. And then you really do want to copy
it, to avoid bugs!
The only exceptions I know of are: NSDate
, and NSAttributedString
.
But don’t just take my word for it! Here’s the snippet of code I used to test all this:
NSMutableArray *objects = [NSMutableArray array];
//add anything that can be made with alloc/init
NSArray *classNames = [NSArray arrayWithObjects:@"NSArray", @"NSColor", @"NSData", @"NSDictionary", @"NSSet", @"NSString", nil];
for(NSString *className in classNames) {
id obj = [[NSClassFromString(className) alloc] init];
if(obj)
[objects addObject:obj];
else
NSLog(@"WARNING: Could not instatiate an object of class %@", className);
}
//manually add objects that must be created in a unique way
[objects addObject:[[NSAttributedString alloc] initWithString:@""]];
[objects addObject:[NSDate date]];
[objects addObject:[NSNumber numberWithInt:0]];
[objects addObject:[NSValue valueWithSize:NSZeroSize]];
//test if retain and copy do the same thing
for(id obj in objects)
if(obj != [obj copy])
NSLog(@"copy and retain are not equvalent for %@ objects", [obj className]);
Best Practices
Get in the habit of using copy
, anytime you need to set
or initWith
something. In general, copy
is safer then retain
, so always prefer it.
I believe it is best to try copy
first. If an object can not be copied, you will find out about it the first time your code is executed. It will be trivial to substitute retain
for copy
. But it is much harder, and takes much longer, to discover that you should have been using copy
instead of retain
.
A program must be correct before it can be made to run faster. And we have seen there is no performance penalty for copy
on most common objects. So it makes sense to try copy
first, and then replace it with retain
if it proves to be necessary through measurement. You will be measuring before you start “optimizing”, right? (I also suspect that if taking ownership of an object is a bottle-neck, then the right optimization is not to switch to retain
, but to find a way to use a mutable object, or an object pool, to avoid the “take ownership” step altogether.)
Choose copy
, unless you have a measurable justification for using retain
.
UPDATE 2009-11-10: Obj-C 2.0 blocks have some peculiarities,
For this reason, if you need to return a block from a function or method, you must [[block copy] autorelease]
it, not simply [[block retain] autorelease]
it.