+3  A: 

It's not just views. It's everything. I don't think even the NSApplication object releases itself.

In fact, I'm pretty sure (but not 100% certain) that the answer to my question #1 is: "Because releasing subviews is unnecessary when the application is about to terminate."

I believe so as well.

If you want to your custom object graph released at quit, have your app delegate own it, and release your other top-level objects in applicationWillTerminate:. As long as you're managing all your ownerships correctly and you release every single top-level custom object from that method, all your custom objects, including views, will die.

Note: I have not tried mixing this with Core Data. It may or may not be possible to do this to your managed objects. I don't have any first-hand experience with that.

Peter Hosey
Re: "If you want to your custom object graph released at quit, have your app delegate own it...": That's what I thought, too, but take a look at the example I gave. parentView is owned by the app delegate, and it is released in applicationWillTerminate, but its subview is never released.
e.James
That… is strange.
Peter Hosey
Logic would seem to indicate that something else must have a retain on the view.
Chuck
@Chuck: You're right. It was the global NSAutoreleasePool that still held ownership of the subview after it had been autoreleased. I posted an answer with all of the dirty details.
e.James
@Peter Hosey: You were right about the termination behaviour, and I upvoted this answer accordingly. I figured out the rest of it after some digging. Thank you for taking the time to answer.
e.James
+1  A: 

In the code you presented you are adding the subview to an ivar called 'view'. Is that what you really did or is it just from copying code to the question?

I ask this because if I create an IBOutlet to the main window's content view and run your code it does what you say. But if I add the myView local var to parentView then it does deallocate:

begin
init <MyView: 0x174460>
init <MyView: 0x174770>
run
quit
dealloc <MyView: 0x174460>
end
dealloc <MyView: 0x174770>

In addition it appears that sub views get autoreleased (adding a log message to autorelease proves it).

Nathan Kinsinger
Good catch. I renamed the variable `parentView` when posting the question in order to make it easier to discuss. I have now fixed it in the question.
e.James
With that change the program will not give the output you showed. There must be something else you are doing and did not show that is, as Chuck pointed out, retaining the subview.
Nathan Kinsinger
+1: I managed to figure out what was going in, but thank you for taking the time to try out the code. You're absolutely right about the autorelease messages being sent to the subviews, and in the end it was the global autorelease pool that wasn't being drained.
e.James
Re: "With that change the program will not give the output you showed." On my machine, it certainly does, and that is the exact code I am using to test. parentView is an ivar of my application delegate, and I do not make it an IBOutlet. myView is not an ivar, and it exists (as a variable) only within applicationDidFinishLaunching. Beyond that, only parentView owns it. Could this behaviour be something new in Xcode version 3.2?
e.James
+2  A: 

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

e.James