Drowning in NSAutoreleasePool
When developing in Cocoa for Mac OS X or for iPhone, it is easy to overlook objects that are dropped in the current NSAutoreleasePool, ultimately leading to a crash. The following article applies to the use of NSAutoreleasePool when garbage collection is not enabled.
Summary
- Preventing over-release
- [ -retain & -release] vs. [ -autorelease]
- Circumventing -autorelease
- NSAutoreleasePool is a stack object
- NSAutoreleasePool pseudo-code
- [pool release] vs. [pool drain]
- External links
1. Preventing over-release
There seem to be some unpredictable stability behavior with objects that are released more than once (objects for which the -retainCount drops below 1). At times, some objects sent too many [ release ] will fail silently, whereas occasionally the host application will crash.
An application must balance the number of -retain and -release sent to a NSObject.
SomeObject * myObj = [[SomeObject alloc] init];
...
[myObj release];
To a certain extent, it would be better if over-release would never crash (akin to [ nil release ]) or always crash, thus not hiding bugs during the development stage. The combination of NSZombie and malloc_history is a wonderful tool to debug over-releasing. Read “Debugging Autorelease” on CocoaDev:
http://www.cocoadev.com/index.pl?DebuggingAutorelease
2. [ -retain & -release] vs. [ -autorelease]
Sending -autorelease to an object allows to literally forget about it. [ someObject -autorelease] ensures that someObject with a -retainCount of 1 will remain retained for the life of the NSAutoreleasePool it belongs to, and will receive a -release message when such pool receives -drain or -release. Generally, the question is “Which NSAutoreleasePool ?”. While the answer is “The most nested one, Sir.”, not knowing may lead to surprising behaviors. A defensive approach may be to only create only one NSAutoreleasePool for the entire duration of the application, but this is certainly a costly and rarely practical answer.
-autorelease, contrarily to -release, can be invoked more than once without ill effects.
For some reason, once an object received the -autorelease message, the operation cannot be undone. There is no -unautorelease message. Nor is there a way to tell the NSAutoreleasePool to just forget about that object.
This can lead to the following situations:
- Preventing to save/release memory occupied by a given object
- Preventing to delete that object at all before deleting the NSAutoreleasePool (sending one-too-many -release will cause the NSAutoreleasePool to send a -release to an already deallocated object, and crash)
- Preventing the deletion of a given NSAutoreleasePool, since one cannot know what’s in it.
Following design patterns while being aware of these limitations is generally not a problem. A suggested approach is to use small scopes with local NSAutoreleasePool any time granularity can be achieved. In Cocoa, Threads do just that.
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
SomeObject * myObj = [[SomeObject alloc] init];
[myObj autorelease]; // No need to remember about that object
...
[pool drain];
}
3. Circumventing -autorelease
Unfortunately, some Cocoa routines will, without a choice, drop objects it creates in the current NSAutoreleasePool.
Example:
UIImage * newImage = [UIImage imageWithCGImage:some_CGImageRef];
As a result, the caller has little or no control of when the new object will be destroyed. It will likely be deallocated when the NSAutoreleasePool to which it belongs is deallocated, which is generally not predictable.
The NSAutoreleasePool seems to be nothing more than a linked list which sends a -release message to all objects it contains (regardless of their state) upon drain. In a managed memory environment, the pseudo-code of NSAutoreleasePool -drain could be simplified like this:
-(void) drain {
[autoreleasePool release];
}
Objects that for which the -retainCount is greater than 1 after the -drain will thus not be released. One can take an object out of a pool by retaining it, then releasing the pool entirely. Such object will be effectively removed from the NSAutoreleasePool.
Example:
UIImage * newImage = nil;
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
newImage = [[UIImage imageWithCGImage:some_CGImageRef] retain];
[pool drain];
}
// The newImage* is now outside of the pool, and retained normally
An example of this technique can be found on Björn Sållarp’s article “UIImage with round corners”:
http://blog.sallarp.com/iphone-uiimage-round-corners
4. NSAutoreleasePool is a stack object
Note that in the example above, a special scope has been declared around the NSAutoreleasePool creation and destruction. This is good practice since the pool is actually a stack object, and must be created and released in the same { }.
This also makes it illegal to create a NSAutoreleasePool in a method, and to release it in another.
This reason alone can serve as a good motivation for creating small, nested NSAutoreleasePool rather than one gigantic one for the life of the Thread.
5. NSAutoreleasePool pseudo-code
In a managed memory environment, the NSAutoreleasePool acts pretty much like a NSArray. The pool maintains strong references to its content. Objects added to the pool receive a -retain message.
Possible pseudo-code for NSAutoreleasePool
// .h
@interface NSAutoreleasePool2 : NSObject {
@private
NSMutableArray * autoreleasePool;
}
-(void) drain;
-(void) addObject:(id)anObject;
-(id) autoreleaseObject:(id)anObject;
@end
// .m
@implementation NSAutoreleasePool2
-(id)init {
if ((self = [super init] ) != nil ) {
autoreleasePool = [[NSMutableArray alloc] init];
}
return self;
}
-(void) dealloc {
[self drain];
[super dealloc];
}
-(void) drain {
[autoreleasePool release];
}
-(void) addObject:(id)anObject {
[autoreleasePool addObject:anObject];
}
-(id) autoreleaseObject:(id)anObject {
if( ! [autoreleasePool containsObject:anObject]) {
[self addObject:anObject];
}
return anObject;
}
@end
Discussion
This is pseudo-code, not actual NSAutoreleasePool source code.
Objects must be added explicitly to this pool (using [pool autoreleaseObject:someObject ]; instead of [someObject autorelease];)
-autoreleaseObject is lenient, and will not penalize objects added more than once.
-addObject is strict ; objects added more than once will receive -release more than once.
-drain and -release do not have the same behavior. This pseudo-code expects an invocation to [pool release ] rather than [pool drain ] to free the NSAutoReleasePool2.
6. [pool release] vs. [pool drain]
In a reference-counted environment sending -release or -drain to a NSAutoreleasePool has the same effect.
In a garbage-collection environment, without NSAutoreleasePool, -release has no effect while -drain triggers garbage collection. For this reason, the developer documentation explicitly recommends using -drain, since it has a meaning in both management models, even on iPhone.
Read more about NSAutoreleasePool -drain on Apple’s developer site:
http://developer.apple.com/mac/library/documentation/cocoa/reference/Foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html
Tweets that mention DEV» Blog Archive » Drowning in NSAutoreleasePool -- Topsy.com said:
Nov 30, 09 at 10:25 pm[...] This post was mentioned on Twitter by Hajime Hirose, sakamoto. sakamoto said: NSAutoreleasePoolな話 http://bit.ly/8FkZTt [...]