2008 10 17Popping a top level autorelease pool
Calling autorelease
on an object will call that object to be release
d 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]; } @endCall
[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.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.
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 !
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.
(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.
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?
> 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.
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.