There is an old feature of Cocoa, not well documented, that bit me
recently:
When you create a new project, Apple gives you a main() that looks like
this:
-----------------
#import <Cocoa/Cocoa.h>
int main(int argc, const char *argv[]){
return NSApplicationMain(argc, argv);
}
-----------------
I'm often tempted to add a little code here, like so:
(toy example)
-----------------
int main(int argc, const char *argv[]){
NSLog(@"now %@", [NSDate date]);
return NSApplicationMain(argc, argv);
}
-----------------
But that puts a message on the console:
-----------------
... *** _NSAutoreleaseNoPool(): Object 0x3042d0 of class NSCFDate
autoreleased with no pool in place - just leaking
-----------------
So I make the obvious fix to make the message go away and get:
-----------------
int main(int argc, const char *argv[]){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"now %@", [NSDate date]);
int retVal = NSApplicationMain(argc, argv);
[pool release];
return retVal;
}
-----------------
But, a quick test shows that "now", below, is never executed.
-----------------
int main(int argc, const char *argv[]){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"then %@", [NSDate date]);
int retVal = NSApplicationMain(argc, argv);
NSLog(@"now %@", [NSDate date]);
[pool release];
return retVal;
}
-----------------
So what's going on here? Well, if you set a breakpoint on exit(), and
just quit the program you'll see that the Quit menu command calls
[NSApplication terminate: ... ];
which in turn calls exit(). If you quit the program by logging out, the
Finder sends your program a "quit" appleEvent, you'll still go through
exit().
I was surprised by this behavior, since MFC, Powerplant, and Carbon all
normally cleanly terminate the runloop and return all the way out.
So how do we fix this?
1.) You can't. There are operations you might desperately want to do
after the user signals he wants to quit, but if the user does a force
quit, or if the machine crashes, those operations aren't going to happen.
2.) Rather than putting code in main(), you can put it in an
+(void)initialize; method of some class. Of course, you can't control
the order that initialize;s get called. If you need a guarantee that you
are run first, you are back to putting code in main() or a more
complicated solution.
3.) You can use atexit() to register a function handler that will be
called from a normal exit().
BUT: NSApplicationMain() does clean up ALL the autorelease pools before
calling exit(), including the one we made before we called
NSApplicationMain().
So the finished code is the rather peculiar looking:
-----------------
static void Finish(void) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"now %@", [NSDate date]);
[pool release];
}
int main(int argc, const char *argv[]){
[[NSAutoreleasePool alloc] init];
atexit(Finish);
NSLog(@"then %@", [NSDate date]);
return NSApplicationMain(argc, argv);
}
-----------------
I suppose the moral of the story is: don't try to do things in main in a
Cocoa program.
Sean McBride - 24 Apr 2008 02:39 GMT
> So what's going on here? Well, if you set a breakpoint on exit(), and
> just quit the program you'll see that the Quit menu command calls
[quoted text clipped - 7 lines]
> I was surprised by this behavior, since MFC, Powerplant, and Carbon all
> normally cleanly terminate the runloop and return all the way out.
It's an optimisation to improve quitting speeds, so say the Cocoa folk.
When you quit an app, -dealloc is also not called for all the various
live objects.
applicationWillTerminate: is often useful for situations like these.
Sean