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

M1 macOS 11.4设备上NSOpenPanel、NSSavePanel调用runModal立即自动取消

NSOpenPanel自动消失/立即返回取消的解决方案

问题原因

1. 事件循环调度冲突

你在openDocument菜单项的回调中同步发送通知、同步调用runModal弹出面板,此时菜单点击的系统事件处理流程还未结束,macOS 11.x(Big Sur)在M1设备上的事件循环调度逻辑与Intel设备存在差异,会主动取消未在事件循环结束后弹出的模态面板,触发立即返回取消的问题。

注意:DispatchQueue.main.async的作用不只是切换到主线程,即使当前已经在主线程,该方法也会把代码逻辑放到下一个RunLoop周期执行,避开当前菜单事件的生命周期,刚好可以解决该冲突。

2. 代码未定义行为

原AppDelegate中发送通知的代码nc.post(Notification(name: Notification.Name("documentOpenRequested"), object: object))里,object变量未声明定义,属于未定义行为,M1的内存模型与Intel架构不同,该异常会导致后续创建的NSOpenPanel对象被系统提前释放。

3. 系统权限/第三方软件干扰

  • 如果用户未给App开放对应目录的文件访问权限,系统会自动关闭打开面板,返回取消结果
  • 窗口管理类、快捷键管理类、安全杀毒类第三方软件可能会主动拦截模态弹窗,触发自动取消

修复方案

第一步:修正通知发送代码

class AppDelegate: NSObject, NSApplicationDelegate {
    @IBAction func openDocument(_ sender: NSMenuItem) {
        let nc = NotificationCenter.default
        // 修正未定义的object参数,不需要传值则填nil
        nc.post(Notification(name: Notification.Name("documentOpenRequested"), object: nil))        
    }
}

第二步:调整面板弹出时机

@objc func doOpenDocument(_ notification: Notification) {
    print ("doOpenDocument called on MainThread: \(Thread.current.isMainThread)")
    var URLToOpen: URL?
    if let selectedURL = notification.object as? URL {
        URLToOpen = selectedURL
    } else {
        // 调度到下一个RunLoop执行,避开当前菜单事件生命周期
        DispatchQueue.main.async {
            let openPanel = NSOpenPanel()
            openPanel.allowsMultipleSelection = false
            openPanel.canChooseDirectories = false
            openPanel.canCreateDirectories = false
            openPanel.canChooseFiles = true
            openPanel.allowedFileTypes = ["sdf", "json", "txt"]
            openPanel.allowsOtherFileTypes = true
            let response = openPanel.runModal()
            if response == .OK, let myURL = openPanel.url {
                URLToOpen = myURL
                NSDocumentController.shared.noteNewRecentDocumentURL(myURL)
            } else {
                print ("RunModal exited with response not OK: \(response.rawValue)")
            }
            guard let theURL = URLToOpen else {
                return
            }
            // 原有处理URL的业务逻辑移到此处
        }
        return
    }
    guard let theURL = URLToOpen else {
        return
    }
    // 处理notification带的URL的逻辑保留在此处
}

第三步:用户侧排查

  • 引导用户检查「系统设置>安全与隐私>文件和文件夹」,确认你的App已获得需要的文件访问权限
  • 引导用户暂时退出第三方窗口管理、快捷键、杀毒类软件,测试问题是否复现

内容的提问来源于stack exchange,提问作者greg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 22:06:04