macOS关于NSWindow被释放后访问Optional崩溃的问题
macOS关于NSWindow被释放后访问Optional崩溃的问题

macOS关于NSWindow被释放后访问Optional崩溃的问题

问题描述

在开发macOS应用时,通常需要使用NSWindow创建各种弹窗或普通窗口。例如:

var TipsAccessibilityWindow: NSWindow? = nil

这是一个可选的提示辅助功能的Window窗口。

当窗口需要关闭时,将该Window窗口设置为nil时,会发生崩溃的问题:

Button(action: {
    // 隐藏窗口
    WindowManager.shared.TipsAccessibilityWindow = nil
},label:  {
    Text("取消")
})

Xcode报错信息为:

Thread 1: EXC_BREAKPOINT (code=1, subcode=0x186aa23c4)

这里的问题:当使用Button设置TipsAccessibilityWindow为nil时,在主线程的操作发现崩溃。

问题原因

当我将TipsAccessibilityWindow设置为nil时,只是断开了窗口的引用,窗口本身仍然存在(没有被关闭、没有被销毁),仍然在屏幕上,系统持有TipsAccessibilityWindow的强引用。

窗口的delegate和各种操作可能会访问TipsAccessibilityWindow,就导致访问已销毁的对象,从而发生崩溃。

macOS 中,窗口的生命周期受运行循环、显示系统、NSApplication 事件系统共同管理。

当设为 nil,只是断开了类中属性的引用,而:

1、系统仍然保有窗口对象;

2、它没有调用 window.close(),没有退出显示栈;

3、它的 delegate 还在指向某个对象;

4、它的事件循环还在运行(点击、关闭按钮、resize 等);

所以:窗口没有关闭,引用没有完全释放,一旦再被系统事件访问,指针就错乱了。

解决方案

在SwiftUI中,使用close()方法,关闭窗口:

Button(action: {
    WindowManager.shared.TipsAccessibilityWindow?.close()
    // 可选:这里不需要再手动 = nil,close 后自动释放(除非你自己保留了强引用)
}, label: {
    Text("取消")
})

关闭后,窗口退出。如果使用NSWindowDelegate代理的话,可以通过windowWillClose方法进行验证:

class WindowManager:NSWindowController,NSWindowDelegate {
    static let shared = WindowManager()
    var TipsAccessibilityWindow: NSWindow? = nil
    
    func windowWillClose(_ notification: Notification) {
        if let window = notification.object as? NSWindow,window == TipsAccessibilityWindow {
            print("当前 TipsAccessibilityWindow 窗口被关闭,清理辅助窗口")
        }
    }
}

当关闭调用close()方法,关闭窗口时,Xcode会输出:

当前 TipsAccessibilityWindow 窗口被关闭,清理辅助窗口

延伸问题

当我们使用close() 方法关闭窗口后,将TipsAccessibilityWindow变量改为nil,这时仍然会触发崩溃问题:

Button(action: {
    WindowManager.shared.TipsAccessibilityWindow?.close()
    WindowManager.shared.TipsAccessibilityWindow = nil  // 设置为nil
},label:  {
    Text("Cancel")
})

这也就意味着,我们在创建TipsAccessibilityWindow窗口后,无法设置为nil,断开引用,即使已经调用close()方法关闭窗口。

当我们添加输出时,也可以验证close()方法关闭窗口在前,TipsAccessibilityWindow设置为nil在后:

Button(action: {
    WindowManager.shared.TipsAccessibilityWindow?.close()
    print("完成窗口的关闭")
    WindowManager.shared.TipsAccessibilityWindow = nil  // 设置为nil
    print("结束Button")
},label:  {
    Text("Cancel")
})

class WindowManager:NSWindowController,NSWindowDelegate {
    static let shared = WindowManager()
    var TipsAccessibilityWindow: NSWindow? = nil
    
    func windowWillClose(_ notification: Notification) {
        if let window = notification.object as? NSWindow,window == TipsAccessibilityWindow {
            print("当前 TipsAccessibilityWindow 窗口被关闭,清理辅助窗口")
        }
    }
}

Xcode输出:

当前 TipsAccessibilityWindow 窗口被关闭,清理辅助窗口
完成窗口的关闭
结束Button

总结

在macOS中,如果想要关闭NSWindow,需要调用NSWindow的close()方法关闭窗口,不能将NSWindow对应的变量改为nil,因为这个设置nil的操作会引发崩溃的问题。

这个崩溃的问题在于,访问已释放的内存(悬空引用)导致的崩溃。

在macOS的窗口系统中,调用 .close() 方法关闭窗口后,如果访问NSWindow对应的变量,变量引用的窗口很可能被系统销毁了部分资源,但Swift仍然保留着dangling pointer(悬空指针)。

即使NSWindow对应的变量是可选的,如果它被某处代码强引用,那么即使设置为nil,背后仍然有引用存在。

因此,正确的做法就是调用close()方法关闭窗口,不把NSWindow对应的变量设置为nil,而是交给生命周期自动释放。

相关文章

1、Swift悬空引用:https://fangjunyu.com/2025/07/28/swift%e6%82%ac%e7%a9%ba%e5%bc%95%e7%94%a8/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注