How to kill NSWindow properly?

Cross posted to https://stackoverflow.com/questions/57916099/how-to-kill-nswindow-properly.


Recently, I discovered surprising behavior of

NSWindow
. It doesn't die while it is displayed on screen regardless of existence of reference to it.
import Foundation
import AppKit


final class W1: NSWindow {
    deinit {
        print("W1.deinit")
    }
}


print("start")
autoreleasepool {
    var w1:W1? = W1()
    w1?.setFrame(NSRect(x: 0, y: 0, width: 100, height: 100), display: true)
    w1?.orderFront(nil)
    w1 = nil
}
print("finish")
RunLoop.main.run()


Code above prints

start
and
finish
but no
W1.deinit
.

I tested this on these platforms.

  • Xcode 10 on Mojave
  • Xcode 11 GM (first) on Catalina Beta

And confirmed same result on both platforms.

Here are my questions.

  • Why
    NSWindow
    don't die?
  • How am I supposed to manage
    NSWindow
    ?
  • How to kill it properly?

As last reference to

NSWindow
has been removed, it is supposed to die immediately. But it doesn't.

If it do not die, it means there's another "hidden" reference to it or AppKit have "special" behavior on windows. What's the reason?

Window dies if I

close()
it before removing last reference to it. But I am not sure whether this is really proper/designed way to kill it as it's out of Cocoa/Swift lifetime management rules.

Replies

I think you already got the correct answer on Stack Overflow. The window is not the same thing as the NSWindow object.


Although the NSWindow object keeps track of the state of the window within your app, the Window Server (a separate process that services the windows of all apps) also manages the window. That's how your windows can be interleaved (front to back order) with windows from other apps without sacrificing privacy.


A long time ago, I read (though I can't remember where) that when your app frees its last reference to the NSWindow object, the object actually stays alive until the Window Server has been notified, which takes an additional iteration of the main thread run loop. In other words, the object won't be deallocated immediately after your app's last reference is released, but sometime later. I don't know if this is still true, but it may be.


If you don't want the window to exist any more, then "close()" is the correct function to call. You should also discard any strong references in your code. After that, it's not your responsibility any more.

I see. Thanks for answer.

It seems there's another owner that I don't know. It would be nice if I can find some document that mentions about another owner.

Note that I'm not claiming that the Window Server "owns" the NSWindow object in the retain/release (memory management) sense. I'm making two slightly different claims:


1. The actual NSWindow object in your app may have its (retain/release) lifetime artifically extended until the Window Server says it's OK to deallocate. This would have to be implemented as an extra owning reference inside your app, but just not inside code you wrote.


2. The actual window may be a "thing" in the WIndow Server separately from the NSWindow object in your app, and the thing's lifetime may be slightly different from the NSWindow object lifetime.


If both of those claims are true, then reasoning about the apparent lifetime of a window from the user's point of view is a little tricky in the edge cases of its lifetime — even though the behavior is deterministic if we could peer into the internals of the Window Server.

The other components that hold a strong reference to your window are the various pieces of the AppKit and operating system components. If you think about how the window is actually created and maintained, all the way down to the screen drawing and compositing operations, it is likely that several components of the architecture maintain a reference in some fashion. When you call close(), you're telling the system you are not interested in the window anymore, and it can release it's references, re-draw the screen, and the like.


The whole subject is somewhat touched in the Windows Programming Guide (archived document in the Developer documentation), but no real specifics since this all considered an implementation detail, and is part of the AppKit black box.