重写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
相关产品推荐
相关产品推荐

