I figured it out. The solution was to create and release my own NSAutoreleasePool
within the applicationWillTerminate:
method.
Details:
Deep in the bowels of NSView
's dealloc
method, all kinds of things are done to remove the view and all of its subviews from the responder chain, set up the next key view, send delegate messages, etc. Somewhere in this code, each subview is sent a retain
message, and later sent an autorelease
message. (Actually, each subview is retained and autoreleased twice - see details below). This is normal, but here's the kicker: When the subviews are sent an autorelease
message, they get added to whatever NSAutoreleasePool
happens to be active at that point in time, and they are kept around until that particular pool goes out of scope. In the case of application termination, the pool they get added to is the one created automatically during each iteration of the application's main event loop, and this pool is never sent a release
message because the application is about to quit!
Experimental results:
I added a bunch of logging messages to the init
, retain
, release
, and autorelease
methods for MyView
, which all have code similar to this:
NSLog(@"[%@ retain]: count = %d", [self name], [self retainCount]+1);
return [super retain];
I also logged {
}
around the code for dealloc
so I could see when the magic happens.
Using these logging messages, here is what happens to my NSView
obejcts:
begin
[parent init]: count = 1
[subview init]: count = 1
[subview retain]: count = 2
[subview release]: count = 1
run
quit
[parent release]: count = 0
[parent dealloc]
{
[subview retain]: count = 2
[subview autorelease]: count = 2
[subview retain]: count = 3
[subview autorelease]: count = 3
[subview release]: count = 2
}
end.
Now, when I use the following code in applicationWillTerminate:
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
NSLog(@"quit");
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[parentView release];
[pool release];
NSLog(@"end.");
}
The result is as follows:
begin
[parent init]: count = 1
[subview init]: count = 1
[subview retain]: count = 2
[subview release]: count = 1
run
quit
[parent release]: count = 0
[parent dealloc]
{
[subview retain]: count = 2
[subview autorelease]: count = 2
[subview retain]: count = 3
[subview autorelease]: count = 3
[subview release]: count = 2
}
[subview release]: count = 1
[subview release]: count = 0
[subview dealloc]
{
}
end.
And you can clearly see the two release
messages sent to the subview by the NSAutoreleasePool
as it drains.
References:
NSView.m from GNUStep
Autorelease Pools from Apple's developer documentation