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

重写NSDocument的save方法后,取消保存无法退出应用的问题

问题分析与解决方案

你遇到的核心问题是:在NSDocument的旧版代理式保存流程中,当你提前返回而不通知系统保存操作的结果时,系统会一直处于等待保存完成的状态,进而阻塞应用退出流程。

为什么会出现这种情况?

当系统触发保存提示(比如退出时的“是否保存”弹窗),NSDocument会进入一个等待状态,它需要你明确告知保存操作是成功、失败还是被用户取消了。如果直接return不调用super.save也不处理代理回调,系统会一直卡在等待状态,导致应用无法正常退出。

修复方案1:正确处理旧版代理回调

在createCustomUrlFrom失败或用户取消时,你需要手动调用代理的didSaveSelector,传递“保存取消/失败”的信号,让系统结束等待状态。修改你的代码如下:

override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, delegate: Any?, didSave didSaveSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) {
    guard let customURL = createCustomUrlFrom(url: url) else {
        // 必须通知代理保存操作已取消/失败
        guard let delegate = delegate, let didSaveSelector = didSaveSelector else {
            return
        }
        
        // 构造取消错误,符合系统标准错误码
        let cancelError = NSError(domain: NSOSStatusErrorDomain, code: userCanceledErr, userInfo: [
            NSLocalizedDescriptionKey: "保存操作已取消"
        ])
        
        // 根据selector的签名传递对应参数(NSDocument的回调有两种常见签名)
        if didSaveSelector.description.contains("error") {
            delegate.perform(didSaveSelector, with: self, with: false, with: url, with: cancelError, with: contextInfo)
        } else {
            delegate.perform(didSaveSelector, with: self, with: false, with: url, with: contextInfo)
        }
        return
    }
    super.save(to: customURL, ofType: typeName, for: saveOperation, delegate: delegate, didSave: didSaveSelector, contextInfo: contextInfo)
}

修复方案2:改用现代异步API(推荐)

苹果现在更推荐使用基于闭包的异步保存API,这种方式更简洁,也不容易出现状态不一致的问题。你可以重写write(to:ofType:completionHandler:)方法:

override func write(to url: URL, ofType typeName: String, completionHandler: @escaping (Error?) -> Void) {
    guard let customURL = createCustomUrlFrom(url: url) else {
        // 直接通过闭包返回取消错误
        let cancelError = NSError(domain: NSOSStatusErrorDomain, code: userCanceledErr, userInfo: [
            NSLocalizedDescriptionKey: "保存操作已取消"
        ])
        completionHandler(cancelError)
        return
    }
    
    // 在这里添加你的自定义逻辑:创建文件夹、复制音频文件到子文件夹等
    do {
        // 创建目标文件夹(包含中间目录)
        try FileManager.default.createDirectory(at: customURL.deletingLastPathComponent(), withIntermediateDirectories: true)
        // 执行音频文件复制逻辑...
        
        // 调用父类方法完成保存
        super.write(to: customURL, ofType: typeName, completionHandler: completionHandler)
    } catch {
        // 处理文件夹创建或文件复制的错误
        completionHandler(error)
    }
}

这种方式下,无论成功还是失败,你只需要通过completionHandler通知系统结果,系统会自动处理后续的退出流程,不会出现阻塞问题。

额外提示

在createCustomUrlFrom中处理用户选择“取消”的场景时,也要确保返回nil或者抛出取消错误,让上层流程能正确捕获并通知系统。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:25:47