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

  1. Preventing over-release
  2. [ -retain & -release] vs. [ -autorelease]
  3. Circumventing -autorelease
  4. NSAutoreleasePool is a stack object
  5. NSAutoreleasePool pseudo-code
  6. [pool release] vs. [pool drain]
  7. 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:

  1. Preventing to save/release memory occupied by a given object
  2. 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)
  3. 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

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 { }. Please read on.


4. NSAutoreleasePool is a stack object

By nature, an NSAutoreleasePool instance is stored on the stack (as opposed to the heap). As a direct consequence, the scope of these pools, and ultimately of the objects they reference, are tightly coupled with the methods that created them. Even though the pools (just like the invocation of methods) are nested, there is no virtual stack of NSAutorelease pools.

As the method that created a given pool goes out of scope, so does that very pool, and all the objects referenced by it will receive a single -release message. For an object to survive a pool drain, it must be explicitly retained. See paragraph 3, Circumventing -Autorelease.

Being a stack object also makes it illegal to create an NSAutoreleasePool instance in a method, and to release it in another. This reason alone can serve as a good motivation for creating small, local and  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

7. External links


DeliciousBookmark this on Delicious

2 Responses to “Drowning in NSAutoreleasePool”

  1. 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 [...]

  2. stackoverflow-pingback said:

    May 12, 10 at 7:26 am

    StackOverflow entry that mentions “Drowning in NSAutoreleasePool”

    … Here’s an article that presents a general overview of NSAutoreleasePools …
    http://stackoverflow.com/questions/1010629/nsautoreleasepool-carrying-across-methods


Leave a Reply

Due to a sudden burst of interest from unidentified aliens, you must now to post a comment.