我正在尝试了解 NSWindows 的正确生命周期以及如何正确处理它们。
我有默认的 macOS 应用程序模板,其中一个窗口内有一个 ViewController,该窗口位于一个窗口控制器内。
我还在
applicationDidFinishLaunching
中创建了一个简单的程序窗口,如下所示:
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)
CustomWindow 类只是:
class CustomWindow: NSWindow {
deinit {
print("Deinitializing window...")
}
}
当我关闭程序窗口时(通过调用
.close()
或仅点击红色关闭按钮,应用程序会因 EXC_BAD_ACCESS 崩溃。即使我没有以任何方式访问该窗口。
有人可能认为这是因为 ARC,但事实并非如此。一——即使
NSApplication.shared.windows
的局部范围结束,窗口仍然被 applicationDidFinishLaunching
强引用。第二,"Deinitializing window..."
仅在窗口关闭后打印。
关闭 Interface Builder 窗口不会出现任何崩溃。我深入挖掘并玩弄了
isReleasedWhenClosed
属性。对于 IB 窗口来说,无论真假都没有区别。不过,它阻止了程序化窗口的崩溃。
但这提出了三个问题:
isReleasedWhenClosed = true
那么您如何实际释放程序化窗口以使其不会无限期地停留在内存中?1。程序化窗口关闭后访问什么——导致崩溃,因为 NSWindow 的默认行为是释放它——如果它不是我的代码?
崩溃的回溯显示该窗口是从自动释放池引用的。该窗口已被释放。
2。普通窗口和窗口控制器内防止这些崩溃的窗口之间有什么区别?
来自
isReleasedWhenClosed
的文档:
但是,对于窗口控制器拥有的窗口,关闭时释放将被忽略。
3.如果程序化窗口的推荐方法是始终设置 isReleasedWhenClosed = true 那么如何实际释放程序化窗口以使其不会无限期地滞留在内存中?
对于编程窗口,建议的方法是将
isReleasedWhenClosed
设置为 false
,以便窗口遵循 ARC 规则。您可以使用 NSWindowDelegate
方法 windowWillClose(_:)
或 willCloseNotification
来释放窗口,以防您保留强引用。
AppKit 保留了窗口。我认为这是为了向后兼容。这解释了为什么当
dummyWindow
超出范围并且窗口被释放时窗口不会消失。
如果您的应用程序在 macOS 10.13 SDK 或更高版本上链接,则 AppKit 将强烈引用订购的 NSWindows,直到它们被显式订购或关闭(不包括隐藏或最小化操作)。这意味着,一般情况下,屏幕上的窗口不会被释放(并且关闭/排序是释放的副作用)。