Retain/Autorelease in Cocoa Getters
Someone asked me: “Why do some getters do:
{
return _object;
}
and others do:
{
return [[_object retain] autorelease];
}
…?”
The difference comes up in a special case which can cause a crash.
Consider this…
- (void)setObject:(MyObject *)object;
{
[object retain];
[_object release];
_object = object;
}
- (id)object;
{
return _object;
}
- (void)dealloc;
{
[_object release];
[super dealloc];
}
@end
@implementation BarClass
- (void)doStuff;
{
// Create a foo
FooClass * foo = [[FooClass alloc] init];
// Set an object on foo
MyObject * firstObject = [[MyObject alloc] init];
[foo setObject:firstObject];
[firstObject release]; // alloc like copy and retain must be balanced
// via release or autorelease so we balance it here.
// Get the object back from foo, and poke it
MyObject * anObject = [foo object];
[anObject pokeWithStick];
// Set another object on foo
MyObject * secondObject = [[MyObject alloc] init];
[foo setObject:secondObject];
[secondObject release];
// Poke the object again
[anObject pokeWithStick]; // Kablamo. Why?
}
@end
When [foo setObject:] is called the second time, _object in foo is released within the setObject: method. In our little example, it’s clear that the retain count at this point would go to zero and it would be deallocated.
firstObject = [MyObject alloc] is retain count 1, then setObject’s [object retain] makes it 2, [firstObject release] decrements it to 1, and when setObject:secondObject is called, [_object release] decrements to 0 and deallocates the object.
anObject still has a pointer to this now-deallocated object, so calling pokeWithStick blows up.
This will only happen if you have a scenario exactly like that above, so 99% of the time, this isn’t a problem. If you’re calling setSomething: twice and inbetween them you grab a reference to something, and use it after the second setSomething, there should immediately be a red flag going off in your head anyway.
BUT, this doesn’t have to be the case.
If we change it up a little bit, consider the following…
- (void)doStuff;
{
// Create a foo
FooClass * foo = [[FooClass alloc] init];
// Set an object on foo
MyObject * firstObject = [[[MyObject alloc] init] autorelease];
[foo setObject:firstObject];
// Get the object back from foo, and poke it
MyObject * anObject = [foo object];
[anObject pokeWithStick];
// Set another object on foo
MyObject * secondObject = [[[MyObject alloc] init] autorelease];
[foo setObject:secondObject];
// Poke the object again
[anObject pokeWithStick]; // Works fine. Why?
}
@end
The only difference is that instead of balancing the alloc with a release, we’re using autorelease instead. That’s it.
However, this time around there is no crash. Why?
In Cocoa, by default there is one autorelease pool created automatically each time around the event loop. Calling -autorelease adds the receiver to this autorelease pool. At the end of the event loop, the autorelease pool itself is deallocated, and each each object in the pool is sent a -release message (thus balancing an earlier retain).
So knowing that, the reason the above does not crash is that the autorelease pool doesn’t send -release to each object until after the doStuff method is done executing, so firstObject is not deallocated when calling setObject the second time.
Walking through it: firstObject = [MyObject alloc] is retain count 1, and -autorelease puts it in the pool. setObject’s [object retain] makes it 2. When setObject:secondObject is called, [_object release] decrements the rc to 1. [anObject pokeWithStick] is called the second time, and works. At the end of the event loop, the object is sent -release, the rc is decremented to 0, and the object is deallocated.
This is all logical when you think about it, but it’s kinda wonky that it crashes if you balance the alloc one way, and works fine the other way. A way to get around this is to make it consistent that the getter always returns an object that is in the autorelease pool. How?
- (void)setObject:(MyObject *)object;
{
[object retain];
[_object release];
_object = object;
}
- (id)object;
{
return [[_object retain] autorelease];
}
- (void)dealloc;
{
[_object release];
[super dealloc];
}
@end
@implementation BarClass
- (void)doStuff;
{
// Create a foo
FooClass * foo = [[FooClass alloc] init];
// Set an object on foo
MyObject * firstObject = [[MyObject alloc] init];
[foo setObject:firstObject];
[firstObject release];
// Get the object back from foo, and poke it
MyObject * anObject = [foo object];
[anObject pokeWithStick];
// Set another object on foo
MyObject * secondObject = [[MyObject alloc] init];
[foo setObject:secondObject];
[secondObject release];
// Poke the object again
[anObject pokeWithStick]; // No Kablamo.
}
@end
Within the -object getter, if we do an extra retain on _object, then balance that with an -autorelease, the object will be in the autorelease pool, so it’ll be safe to use whereever whenever up until the pool is released.
firstObject = [MyObject alloc] is retain count 1, then setObject’s [object retain] makes it 2, [firstObject release] decrements it to 1. When anObject = [foo object] is executed, -object calls retain (increments to 2) then calls -autorelease which puts it in the pool. When setObject:secondObject is called, [_object release] decrements it to 1. At the end of the event loop, the autorelease pool is deallocated, and the object is sent -release again, decrementing the reference count to 0 and deallocating the MyObject instance.
So it can be beneficial to do
return [[_object retain] autorelease];
in your getters, instead of
return _object;
but it’s not standard to do it either way, so as a user of an API it’s still best not to rely on getters handing you an object that is in an autorelease pool, but it *perhaps* is good as an API provider to hand back an object in a pool from a getter just incase the API user is relying on it (intentionally or unintentionally) to prevent a possible crash.
A real world example of this would be NSDictionary. Consider:
@"If you were a chicken, you'd be impeccable.", @"pickupLine", nil];
NSString * pickupLine;
pickupLine = [dict objectForKey:@"pickupLine"];
NSLog(@"%@", pickupLine);
[dict setObject:@"If you were a booger I'd pick you first." forKey:@"pickupLine"];
NSLog(@"%@", pickupLine); // Kaboom. objectForKey merely returns a reference
NSDictionary’s objectForKey: method simply returns a reference to the object because when calling it many times, it’s more efficient.
The Another way to get around this crash would be to use -autorelease in the setObject: method instead of -release. For example…
{
[object retain];
[_object autorelease];
_object = object;
}
- (id)object;
{
return _object;
}
This is likely more efficient because you’re much more likely to get an object then you are to set it, so the fewer calls to -autorelease the better. But of course, situations will differ so you may have a case where the set is called far more than the get and find that it’s less efficient to do it this way. It’s just something to keep in mind when you read or write code that uses this autorelease technique.







Comments(0)