Parmanoir

Popping a top level autorelease pool

See also When does autorelease release ?

Calling autorelease on an object will call that object to be released just after the current run loop has finished running the current event, for example after your mouseDown returns control back to the runtime. That way, autoreleased objects get killed quickly.

BUT creating a “top level” autorelease pool in main.m before NSApplicationMain starts will cause objects to be released after NSApplicationMain returns, that is when your application quits and you release the pool you created.

int main(int argc, char *argv[])
{
	// Create our pool
	id pool = [[NSAutoreleasePool alloc] init];

	// Call some code that will alloc and autorelease objects
	...

	// Run app
	int r = NSApplicationMain(argc,  (const char **) argv);

	// Release our pool : objects allocated and autoreleased before NSApplicationMain will get killed here
	[pool release];
	return	r;
}

This can be problematic if you want your objects to be released sooner. Luckily, we can create and destroy autorelease pools whenever we want, so we can release that first pool as long as we keep a global reference to it.

@implementation MyAutoreleasePool
static id autoreleasePool;
+ (void)allocAutoreleasePool
{
	autoreleasePool = [[NSAutoreleasePool alloc] init];
}

+ (void)deallocAutoreleasePool
{
	[autoreleasePool release];
}
@end
Call [MyAutoreleasePool allocAutoreleasePool] in main.m, then call [MyAutoreleasePool deallocAutoreleasePool] in (for example) the application delegate's awakeFromNib. You'll then need to create a new autorelease pool, and that one will behave like you expect : killing objects on time.

Jordy/Jediknil
2008 10 17

shudders This is awful...so many Cocoa classes depend on an autorelease pool being around. Autorelease pools stack anyway; if you really care about this, just create a new one in -awakeFromNib or -init. Except...then you might end up with your app delegate effectively holding onto its own reference. (You have that with your scheme as well.)

Why would you need such a thing? You shouldn't really be relying on -dealloc being executed anyway...that's what -applicationWillTerminate: is for.

Jordy/Jediknil
2008 10 17

Oh, not to mention that AppKit creates and releases an autorelease pool around every run-loop cycle. It's not like random objects are sticking around anyway.

Patrick Geiller
2008 10 17

I care about this because JSCocoa loads .js classes used by NIBs, and they must be created before NSApplicationMain loads the NIB. Using a top level autorelease pool will make some objects stick around until program end.

Popping the top pool kills objects when they're not needed anymore instead of killing them at program end. As JSCocoa logs instance count for debugging purposes, it allows me to check JSCocoa's GC logic.

The issue here is to have autoreleased objects used before NSApplicationMain, and discarded before program end. If you have a better solution I'll take it !

Jordy/Jediknil
2008 10 18

Well...I would create an autorelease pool around the JSCocoaController initialization and call to evalJSFile:. (I'm not familiar with JSCocoa, true, so maybe this isn't quite what you want.)

If you mean you need the objects to hang around until after the nib is loaded, well, I'm not sure you should be using autorelease then anyway; it breaks the semantics of "I'm done with this, if the caller wants it they better retain it". Classes in particular are supposed to stick around for the lifetime of the program.

I was going to suggest a safer variant which deallocated and reallocated the autorelease pool at the same time, but then I realized that both versions have a serious problem: when -awakeFromNib is called, AppKit has its own autorelease pool on the top of the autorelease stack. The pool you release is beneath that. If, by chance, the program pops its pool and does something, the "active" autorelease pool will have died already.

Try putting an NSLog([NSString stringWithString:@"hi"]); after your call to NSApplicationMain() in a JSCocoa app. If my suspicion is correct it'll crash or at the very least log an error...even if you put a third autorelease pool beneath the one from your method (e.g. [NSAutoreleasePool new] as the first line of your main function).

I realize that's a real edge case, and that it may not really affect JSCocoa programs. But the bending of the autorelease pool conventions makes me cringe. At the very least I would leave a normal pool in place beneath your "remote-control" pool.

Patrick Geiller
2008 10 18

(I autorelease objects for convenience, eg in getProperty(), allocating a string to copy the property name from JS back to ObjC then autoreleasing it as I don't want to release it at each return of the function or use goto.)

> I would create an autorelease pool around the JSCocoaController initialization and call to evalJSFile:

evalJSFile will create ObjC classes that will be called back during NIB loading. Creating an autorelease pool just before calling, calling, then releasing the pool would remove the autorelease objects — that's what I want. This would work fine if I'm the only one autoreleasing, but the runtime might autorelease too, or the .js might be using functions like performSelector:withObject:afterDelay that would maybe leak if they happen before the runtime creates a new pool.

> Try putting an NSLog([NSString stringWithString:@"hi"]); after your call to NSApplicationMain() in a JSCocoa app.

You mean right in main.m ? Doesn't crash nor even log. Even a basic NSLog() fails to display anything — nothing shows up in the console or in console .app .

> I realize that's a real edge case, and that it may not really affect JSCocoa programs. But the bending of the autorelease pool conventions makes me cringe. At the very least I would leave a normal pool in place beneath your "remote-control" pool.

I don't think autorelease pools have any concept of 'beneath', just a stack getting pushed and popped by every pool alloc/dealloc. What I'm doing is popping that stack leaving it empty, then right after adding a new pool to it. That's why your NSLog doesn't crash as that pool is still in place. Which, thinking about it, might be BAD :) It should be released too after NSApplicationMain.

//

I'm well aware this is kludgy, but it works :) A cleaner solution would be to not use any JSCocoa objects in MainMenu .nib, and starting JSCocoa in the application delegate's awakeFromNib. I really like having an app delegate written in Javascript, but I might use a 'proxy' ObjC one and replace it with a JS one in awakeFromNib. I think I'll go down that route.

luz
2008 10 18

iPhone app templates already have that global autorelease pool in main.m your're suggesting. However, ironically it is not effective at all, as iPhone apps terminating do no exit from UIApplicationMain, but via an exit() somewhere within UIApplicationMain. I thought this was a bug a while ago reported it to Apple. Their answer was: this is by design, do not rely on app exiting from UIApplicationMain, the place to do cleanup is applicationWillTerminate: and nowhere else.

I'm also not convinced creating and deallocating a global pool from an arbitrary point within your program flow really works. IMHO ugly things happen if you release a pool down in the stack while others are still on top. And how do you know how many pools are open at the time you want to call your +deallocAutoreleasePool?

Patrick Geiller
2008 10 18

> Their answer was: this is by design, do not rely on app exiting from UIApplicationMain, the place to do cleanup is applicationWillTerminate: and nowhere else.

Hahaha ! What an answer ! :)

> I'm also not convinced creating and deallocating a global pool from an arbitrary point within your program flow really works. IMHO ugly things happen if you release a pool down in the stack while others are still on top.

Yes, this is ugly. I'll most likely create my JSCocoaController later in the code.

Regarding what happens, we can look at the gnustep code : http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Source/NSAutoreleasePool.m?rev=26258&view=markup — releasing one pool deallocates all its children. Maybe the Apple runtime works the same way. In any case, it doesn't crash and does kill all objects.


Follow me on Twitter
Planet Cocoa
Cocoa.fr

2011 02 22Distance field
2010 07 202Binding through NSApp
2010 05 122Forwarding invocations
2010 02 272Core Image black fringes
2010 02 21Quickest Way to Shell
2010 02 08Who's calling ?
2009 09 2138 ways to use Blocks in Snow Leopard
2009 08 182Bracket Mess
2009 08 124Taming JavascriptCore within and without WebView
2009 04 15Debugging with Activity Monitor
2009 03 25How Core Image Color Tracking works
2009 03 1510Custom NSThemeFrame
2009 03 10Which framework is running ?
2009 03 074CoreUI can paint pretty big
2009 02 18Localization with functions
2009 01 30Did you forget to nest alloc and init?
2009 01 16JSCocoa on the iPhone
2009 01 11Mixing WebView and JavascriptCore
2009 01 09Badge overflow
2009 01 09Find your Garbage Collection leaks with Instruments

Powered by MediaWiki