In Objective-C, retained object properties are quite common:
@class MyOtherObject;
@interface MyObject : NSObject
{
MyOtherObj* mOtherObj;
}
@property (nonatomic, retain) MyOtherObject* otherObj;
- (void) doSomeRandomThings;
@end
@implementation MyObject
@synthesize otherObj = mOtherObj;
// ...
@end
Well, that’s all well and good, but what does that @synthesize
actually do?
You might think that it does something like this:
// The "getter"
- (MyOtherObject*) otherObj
{
return mOtherObj;
}
// The "setter"
- (void) setOtherObj:(MyOtherObject*)otherObj
{
[otherObj retain];
[mOtherObj release];
mOtherObj = otherObj;
}
And you’d be at least close. But that getter has a “gotcha” that might be obvious to some coders, and invisible to others.
The Bug
If we use this code as-is, we are subject to the following bug:
// Fetch myObj.otherObj:
MyOtherObj* otherObj = myObj.otherObj;
// Do some other things, including maybe:
[myObj doSomeRandomThings];
// Now use otherObj:
[otherObj doCoolStuff];
Looks fine, right?
Nope.
It crashes (sometimes?) on that last line, with something
like EXC_BAD_ACCESS
or
an NSInvalidArgumentException
where an unrecognized selector
(guess which selector? right: it’s @selector(doCoolStuff)
) was sent
to some random object you’ve never heard of.
A zombie dereference.
How It Happens
But how is otherObj
a zombie here?
Here’s how. Let’s take a look at -[MyObject doSomeRandomThings]
:
- (void) doSomeRandomThings
{
self.otherObj = [[[MyOtherObject alloc] init] autorelease];
}
Whoops. The old MyOtherObject that used to be referenced by otherObj
has now been released, and the user has had it yanked out from under them.
How To Fix It
Let’s adjust the property getter:
- (MyOtherObject*) otherObj
{
return [[mOtherObj retain] autorelease];
}
Problem solved.
Explanation
The return mOtherObj;
idiom is very common, but it’s unsafe, for exactly the
reason pointed out here. You’re returning the object you have now, and you know
it’s valid now because you’re using it internally, but you really can’t make any
guarantees about how long it will be valid, since something else might mutate
your internal state.
The return [[mOtherObj retain] autorelease];
idiom is safer, because
it adds a temporary extra reference on the object, in addition to the one
you have internally, which is guaranteed to be valid at least until the
current autorelease pool drains (which is certainly going to be at least
until your caller returns, unless he is managing his own autorelease pools,
in which case we hope he knows what he’s doing).
Pros and Cons
Some will argue that this idiom is wasteful, because it fills up the
autorelease pools with objects that may live longer than they need to;
that instead of making your getters do the [[mOtherObj retain] autorelease]
dance,
the callers should instead retain the objects they receive from getters
and release them manually (or autorelease them themselves), since they
know better what their needs are regarding the lifetime guarantees of
objects they get. I disagree strongly, because I think that that is a brittle
model. I think that the only time that this is likely to cause a real
memory problem (a temporary increase in working set size) is when the
caller is doing something in a loop for a potentially large number of
iterations without returning to the main event loop, and if they’re doing
that, they probably ought to be creating and draining their own autorelease
pools for the loop anyway.