问题描述
在创建NSWindow的过程中,使用NSWindow.contentView设置视图:
let hostingView = NSHostingView(rootView:
image
.resizable()
.scaledToFit()
.frame(minWidth: 400, minHeight: 400)
.padding()
)
let window = NSWindow()
window.contentView = hostingView
window.title = "Image Preview"
window.setContentSize(NSSize(width: 600, height: 600))
window.styleMask = [.titled, .closable, .resizable]
window.center()
window.makeKeyAndOrderFront(nil)
self.window = window
当我尝试关闭窗口时,发现NSWindow创建的窗口发生崩溃。

问题复现
在复现问题时,发现当创建第一个NSWindow时,关闭窗口不会报错。
当创建第二个NSWindow或者关闭第二个NSWindow时,Xcode会立即崩溃并弹出报错信息:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

这个问题在于,我把 NSHostingView 直接赋给了 NSWindow.contentView,但是没有托管在一个 NSViewController 中,导致窗口关闭时内存释放顺序错误(野指针),从而崩溃。

解决方案:使用NSWindow(contentViewController:)初始化方法,而是而不是直接初始化NSWindow:
let hostingViewController = NSHostingController(rootView:
image
.resizable()
.scaledToFit()
.frame(minWidth: 400, minHeight: 400)
.padding()
)
let window = NSWindow(contentViewController: hostingViewController)
这一问题在于,我使用NSHostingController将SwiftUI桥接到AppKit中使用。
当我只设置NSWindow.contentView为NSView后,窗口不知道谁是“视图控制器”,导致窗口生命周期无法正常结束,因此第二次打开/关闭图片时,就会发生崩溃。
当使用contentViewController绑定NSViewController后,窗口关闭后会自动清理视图,因此不会发生崩溃。
总结
在AppKit中,一个NSWindow管理的是一个完整的UI层级。

NSWindow只负责“容器”,所以需要一个控制器来管理内容。
NSViewController是逻辑与生命周期的管理者,NSView是具体内容。
当跳过NSViewController直接在NSWindow中设置内容视图是:

这时NSWindow不知道谁在管理视图。
当窗口关闭时,窗口实际上并没有立即释放只是隐藏了。所以self.window还持有window的强引用,于是旧的 NSHostingView 的 SwiftUI runtime 仍然在运行 —— 但它的 window 指针已经被系统释放(AppKit 清理了窗口资源)。
因为窗口并没有销毁,其内部的 SwiftUI 生命周期也无法正确释放,这本质上是 AppKit 与 SwiftUI 生命周期模型不兼容所致。
最终导致关闭窗口时,SwiftUI 内部残留的视图树引用访问了已经释放的对象(如 layout 引擎、host window、环境值等),从而导致:
EXC_BAD_ACCESS (code=1, address=0x20) // 访问了已经释放的地址(野指针)。
当使用NSViewController管理时,它托管了 SwiftUI 的生命周期。

当窗口关闭,系统会自动调用 viewWillDisappear → deinit。
所有 SwiftUI 状态、绑定、视图树都会 安全、明确、同步地销毁。
不会再有悬空引用。