You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

代码创建的NSWindow关闭时崩溃的原因及相关问题咨询

NSWindow生命周期崩溃问题排查与疑问

我正在学习NSWindow的正确生命周期及处理方式,使用默认macOS应用模板(包含Window Controller管理的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...")
    }
}

关闭该代码创建的窗口时,应用会触发EXC_BAD_ACCESS崩溃,即使没有主动访问该窗口。排除ARC问题的两个依据:

  • applicationDidFinishLaunching局部作用域结束后,窗口仍被NSApplication.shared.windows强引用
  • "Deinitializing window..."仅在窗口关闭后才打印

对比Interface Builder(IB)创建的窗口:关闭不会崩溃,修改isReleasedWhenClosed属性对其无影响;但将代码创建的窗口isReleasedWhenClosed设为false可阻止崩溃。

由此产生三个疑问:

  1. 若不是我的代码,那是什么在窗口关闭后访问它,导致因NSWindow默认释放行为而崩溃?
  2. Interface Builder创建的窗口与Window Controller管理的窗口在底层有何区别,从而避免崩溃?
  3. 若代码创建窗口的推荐做法是始终设置isReleasedWhenClosed = false,那么如何正确释放该窗口以避免内存泄漏?

问题解答

1. 崩溃原因:AppKit内部的延迟访问

当isReleasedWhenClosed为默认值true时,窗口关闭后会立即被ARC释放,但AppKit内部可能存在尚未完成的异步操作(比如窗口关闭动画的收尾逻辑、事件队列中的残留回调),这些操作会尝试访问已释放的窗口对象,从而触发EXC_BAD_ACCESS野指针崩溃。你看到deinit打印在崩溃前,说明窗口确实已经被销毁,后续AppKit的内部代码仍在引用这块已回收的内存地址。

2. IB窗口与代码创建窗口的核心区别:Window Controller的持有管理

IB创建的窗口默认由NSWindowController持有管理,这是两者的核心差异:

  • Window Controller会强引用其window属性,即使窗口关闭,只要Window Controller未被释放,窗口对象就不会被销毁
  • Window Controller会接管窗口的生命周期逻辑,包括处理关闭后的资源清理,避免AppKit内部出现野指针访问
  • 此外,IB创建窗口时,AppKit会自动设置内部关联标记(绑定到Window Controller),使其关闭时的释放逻辑被改写,不受isReleasedWhenClosed属性的直接影响

3. 代码创建窗口的正确释放方式

如果设置isReleasedWhenClosed = false,需要手动管理窗口生命周期以避免内存泄漏,推荐两种可靠方案:

方案一:用Window Controller托管

代码创建窗口时,将其关联到NSWindowController,由Window Controller持有窗口引用。当窗口不再需要时,释放Window Controller即可自动回收窗口:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    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.isReleasedWhenClosed = false
    
    let windowController = NSWindowController(window: dummyWindow)
    windowController.showWindow(nil)
    
    // 若需长期持有,可将windowController存储为类属性;无需时置为nil即可释放窗口
}

方案二:监听关闭事件手动释放

给窗口添加NSWindow.willCloseNotification监听,在窗口关闭后将自身持有的窗口引用置为nil:

class AppDelegate: NSObject, NSApplicationDelegate {
    var dummyWindow: CustomWindow?
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let window = CustomWindow(contentRect: .init(origin: .zero, size: .init(width: 200, height: 100)), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: true)
        window.title = "Code window"
        window.isReleasedWhenClosed = false
        window.makeKeyAndOrderFront(nil)
        dummyWindow = window
        
        NotificationCenter.default.addObserver(self, selector: #selector(windowWillClose(_:)), name: NSWindow.willCloseNotification, object: window)
    }
    
    @objc func windowWillClose(_ notification: Notification) {
        dummyWindow = nil
        NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: notification.object)
    }
}

这种方式需确保你持有窗口的唯一强引用,置为nil后ARC会自动回收窗口对象。


内容的提问来源于stack exchange,提问作者Vojta Böhm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 20:03:13