The short version:
release
message when a Cocoa application terminates?
An example:
The MyView
class shown below is nothing more than an NSView
subclass that reports to the console when it is created and destroyed. I have tested it out and found it to work properly. However, when I use it as shown in the next code snippet from my application delegate, I see something unexpected (see sample output).
// MyView:
@interface MyView : NSView { }
@end
@implementation MyView
- (id)initWithFrame:(NSRect)frameRect
{
if ((self = [super initWithFrame:frameRect]) == nil) { return nil; }
NSLog(@"init %@", self);
return self;
}
- (void)dealloc
{
NSLog(@"dealloc %@", self);
[super dealloc];
}
@end
// Application delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(@"begin");
parentView = [[MyView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
MyView * myView = [[MyView alloc] initWithFrame:NSMakeRect(10, 10, 80, 80)];
[parentView addSubview:myView];
[myView release];
NSLog(@"run");
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
NSLog(@"quit");
[parentView release];
NSLog(@"end.");
}
This application produces the following output:
begin
init<
MyView: 0x10013f840>
init<
MyView: 0x10261b620>
run
quit
dealloc<
MyView: 0x10013f840>
end.
The problem:
I can clearly see that the first view object is being released when the application quits, and I'm certain (tested and verified) that NSView
objects automatically release their subviews when they themselves are released. However, it appears that during application termination those subviews are not being released.
The long version: (AKA why on earth would anyone care? :)
Let me start by saying that I am familiar with the way memory is freed by a running application when it quits. I know that my subviews will be properly disposed of, even if they are never sent a release
message, so I'm not worried about this being a leak. 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 use some simple hand-rolled code to do memory tracking while my application is running in debug mode. I make a calls to a Trace_Init()
and Trace_Dealloc()
method in the init
and dealloc
methods of all of my custom classes, and I use the atexit()
function to report any unreleased objects after the Cocoa portion of my application has finished. I find this to be much simpler than running Apple's memory leak performance tool on a regular basis. If I cause a memory leak while running, I'll know about it as soon as my application quits.
However, the lack of a dealloc
call during termination means that any of my custom NSView
subclasses that are used as subviews show up as memory leaks when I quit the application. Thus the reason for my question #2. I would like to have Cocoa release everything during termination so that my memory tracking is able to wrap up properly. Naturally, I would only override the default behaviour in debug mode. My released app has none of the memory tracking code enabled, and should be able to quit itself as efficiently as normal.
That's it! (phew) If you made it this far, thank you for taking the time to read it all.
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
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With