I am trying to wrap my head around proper lifecycles of NSWindows and how to handle them properly.
I have the default macOS app template with a ViewController inside a window that is inside a Window Controller.
I also create a simple programatic window in applicationDidFinishLaunching
like this:
let dummyWindow = CustomWindow(contentRect: .init(origin: .zero, size: .init(width: 200, height: 100)), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: true)
dummyWindow.title = "Code window"
dummyWindow.makeKeyAndOrderFront(nil)
The CustomWindow class is just:
class CustomWindow: NSWindow {
deinit {
print("Deinitializing window...")
}
}
When I close the programatic window (either by calling .close()
or by just tapping the red close button, the app crashes with EXC_BAD_ACCESS. Even though I am not accessing the window in any way.
One might think it's because of ARC but it's not. One—the window is still strongly referenced by NSApplication.shared.windows
even when the local scope of applicationDidFinishLaunching
ends. And two—the "Deinitializing window..."
is only printed after the window is closed.
Closing the Interface Builder window works without any crashes. I dug deep and played with the isReleasedWhenClosed
property. It made no difference whether it was false or true for the IB window. It stopped the crashing for the programmatic window though.
But this raises three questions:
- What is accessing the programatic window after it's closed—causing a crash because the default behaviour of NSWindow is to release it—if it's not my code?
- What is the difference under the hood between a normal window and a window inside a window controller that prevents these crashes?
- If the recommended approach for programmatic windows is to always set
isReleasedWhenClosed = true
then how do you actually release a programatic window so that it does not linger in memory indefinetely?
If the EXC_BAD_ACCESS means that an object is double de-allocated then that would mean that .close() both releases the window (first release) and removes it from the window list which would mean last strong reference is released and ARC cleans the window out (second release).
The theory is supported by me calling .orderOut() instead of close which only removes it from the application list and that does indeed release it without crash. Does this mean programmatic windows should override the close() instance method to call orderOut() instead?
This seems like poor API design or I am understanding it wrong?