Search code examples
objective-ccocoaobject-lifetime

Is there a reliable way to destroy private data structures when a standard NSView or NSWindow is destroyed?


I am developing a GUI framework for another programming language which lets me target native backends, namely the Windows API on Windows, Cocoa on Mac OS X, and GTK+ on other Unix systems. The actual guts of the framework will still be in C and Objective-C. I know what I'm doing is counter to the normal Cocoa way, but I'm not aware of a better solution for the other platforms.

With every UI object (windows, buttons etc.) I need a certain number of private data structures, usually one or two. You can think of these as the data fields of the controller object. There are functions uiWindowDestroy() and uiControlDestroy() which destroy a window/control right then and there, freeing the data structures in the process. (In the case of Objective-C, freeing the control is implemented by calling release.) The data structures should also be freed when the window they are in is closed (not just hidden/ordered out) or the window/control they are a child of is destroyed explicitly. On Windows I do this by handling WM_DESTROY; on GTK+ I do this by connecting to "destroy". Both of these options will handle both case.

I'm not sure how to handle this with Cocoa. I'm aware that the controller/view relationship is supposed to be the opposite. I know with such a relationship my explicit-destruction functions are easy, but automatic destruction with a parent object isn't obvious to me.

Right now, this is what I've tried: My NSWindow uses a custom NSView subclass as the content view which triggers its children destruction in dealloc. I've also subclassed NSButton to free its data structures in dealloc. Finally, my NSWindow has setReleasedWhenClosed:YES called on it and the window delegate calls [self relase] in windowWillClose:. All of these (sub)classes have special code in alloc and dealloc that print that such an allocation/deallocation is taking place.

Here's what I get on 10.9 when I close the window (which has two buttons and a pseudocontrol not backed by a NSView) by clicking the close button in the titlebar:

0x7fce53c1a030 alloc uiWindow            (window private data; malloc())
0x7fce53c3d670 alloc uiContainer         (window content view; NSView)
0x7fce53c0eed0 alloc uiWindowDelegate    (window delegate; NSObject)
0x7fce53c0f770 alloc stack               (viewless control; malloc())
0x7fce53c195f0 alloc uiSingleViewControl (button private data; malloc())
0x7fce53c0abb0 alloc uiSingleViewControl (button private data; malloc())
0x7fce53c3d0e0 alloc uiControl *[]       (viewless control private data; malloc())
0x7fce53c1b8b0 alloc int[]               (same)
0x7fce53c0ed10 alloc intmax_t[]          (same)
0x7fce53c3bda0 alloc intmax_t[]          (same)
0x7fce53c1a030 free
0x7fce53c0eed0 free

As you can see, none of the custom controls are being destroyed along with the NSWindow.

I found this question which suggested that in the case of application termination the default autorelease pool isn't drained; I tried wrapping my various release calls in @autoreleasepool blocks and that didn't work either; the output was the same.

So what I'd like to know is if there's a reliable way to do what I want to do. I know there's no notification sent when an object is released and no way to watch for one, and I'm confused as to why my helper Objective-C objects aren't being deallocated. Ideally, I would avoid the need to subclass NSButton and others at all...

Thanks for understanding.


Solution

  • If a window is controlled by a window controller (instance of NSWindowController or a subclass), then setReleasedWhenClosed:YES has no effect. The window controller has a strong reference to its window.

    Similarly, view controllers have strong references to their views.

    If you want to release the window when it's closed, be sure to release the window controller in -windowWillClose: or otherwise in response to the NSWindowWillCloseNotification notification. Similarly, if you have view controllers, you should release those (not the views they control) when you are done with them.

    All of that said, though, you should generally not predicate control code on memory management. Because you don't know what, besides your code, may maintain strong references to objects, you can't know that releasing your references will cause the objects to be deallocated.

    Instead, you should put such code in notification handlers or delegate methods, like -windowWillClose: or NSWindowWillCloseNotification.

    For views, you can override -view[Will|Did]MoveTo[Window|Superview][:].

    If and when you do respond to such a notification or method invocation and do irreversible cleanup, be sure to clear relevant properties so it doesn't get repeated. For example, set delegates to nil, remove observers, etc.