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

Swift文件实际存在却报‘文件不存在’错误的技术求助

解决SwiftUI文件选择后复制提示“文件不存在”的问题

问题核心原因

你遇到的问题本质是安全作用域资源的访问生命周期错误:对File Provider(如iCloud、第三方文件应用)中的文件进行复制时,权限被提前收回,导致系统无法访问文件内容。

看你的代码流程:

  1. 调用startAccessingSecurityScopedResource()获取文件权限
  2. 调用manageFilePicked执行复制操作
  3. 复制操作尚未完成(即使是同步方法,File Provider文件可能涉及后台内容加载),就立刻调用stopAccessingSecurityScopedResource()收回权限

虽然manageFilePicked是同步方法,但对于远程/云存储的File Provider文件,copyItem(at:to:)底层依赖持续的安全作用域权限才能完成内容加载与复制,提前收回权限会触发系统判定“文件不存在”。

另外,你在获取权限前就将URL存入observedModel.selectedFile,虽然不会直接报错,但不符合安全作用域的最佳实践。

解决方案

1. 调整安全作用域权限的覆盖范围

将stopAccessingSecurityScopedResource()的调用延迟到所有文件操作(复制、分析)完成之后,同时确保错误路径下也能正确释放权限:

修改manageSelectedFile方法

private func manageSelectedFile(url: URL) {
    print("1.AnalyzeFileView::manageSelectedFile.url=[\(url)]")
    guard url.startAccessingSecurityScopedResource() else {
        manageError(NSError(domain: "YourAppDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取文件访问权限"]))
        return
    }
    print("2.AnalyzeFileView::manageSelectedFile.url=[\(url)]")
    observedModel.selectedFile = url
    
    // 通过do-catch+finally确保权限一定会被释放
    do {
        try observedModel.presenter?.manageFilePicked(url: url)
        // 如果文件分析是同步操作,需放在此处,确保权限覆盖整个流程
    } catch {
        manageError(error)
    } finally {
        url.stopAccessingSecurityScopedResource()
    }
}

修改manageFilePicked方法,使其支持抛出错误

func manageFilePicked(url: URL) throws {
    let fileManager = FileManager.default
    let directoryPaths = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
    guard let publicDocumentsDir = directoryPaths.first else {
        throw NSError(domain: "YourAppDomain", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法获取文档目录"])
    }
    print("3.manageFilePicked.pathToFilePicked=[\(url.absoluteString)]")
    let destinationURL = publicDocumentsDir.appendingPathComponent("temporaryfile.\(url.pathExtension)")
    
    print("4.manageFilePicked.destinationURL=[\(destinationURL)]")
    if fileManager.fileExists(atPath: destinationURL.path) {
        print("4.1.manageFilePicked.trying to remove item at=[\(destinationURL.path)]")
        try fileManager.removeItem(atPath: destinationURL.path)
    }
    
    print("5.manageFilePicked.trying to copy item from [\(url)] to [\(destinationURL)]")
    try fileManager.copyItem(at: url, to: destinationURL)
    self.pathFromFileBrowser = destinationURL.path
    print("5.1.manageFilePicked.pathFromFileBrowser=[\(pathFromFileBrowser ?? "nil")]")

    // 同步文件分析操作放在此处
}

2. 异步分析场景的适配

如果文件分析是异步操作,不能在finally中立刻释放权限,需通过回调延迟释放:

修改manageFilePicked为异步回调模式

func manageFilePicked(url: URL, completion: @escaping (Result<String, Error>) -> Void) {
    let fileManager = FileManager.default
    let directoryPaths = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
    guard let publicDocumentsDir = directoryPaths.first else {
        completion(.failure(NSError(domain: "YourAppDomain", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法获取文档目录"])))
        return
    }
    let destinationURL = publicDocumentsDir.appendingPathComponent("temporaryfile.\(url.pathExtension)")
    
    DispatchQueue.global(qos: .utility).async {
        do {
            if fileManager.fileExists(atPath: destinationURL.path) {
                try fileManager.removeItem(atPath: destinationURL.path)
            }
            try fileManager.copyItem(at: url, to: destinationURL)
            self.pathFromFileBrowser = destinationURL.path
            
            // 执行异步分析操作
            self.analyzeFile(at: destinationURL) { _ in
                completion(.success(destinationURL.path))
            }
        } catch {
            completion(.failure(error))
        }
    }
}

修改manageSelectedFile处理异步回调

private func manageSelectedFile(url: URL) {
    print("1.AnalyzeFileView::manageSelectedFile.url=[\(url)]")
    guard url.startAccessingSecurityScopedResource() else {
        manageError(NSError(domain: "YourAppDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取文件访问权限"]))
        return
    }
    print("2.AnalyzeFileView::manageSelectedFile.url=[\(url)]")
    observedModel.selectedFile = url
    
    observedModel.presenter?.manageFilePicked(url: url) { result in
        defer {
            url.stopAccessingSecurityScopedResource()
        }
        switch result {
        case .success(let path):
            // 处理成功结果
        case .failure(let error):
            self.manageError(error)
        }
    }
}

额外注意事项

  • 所有对安全作用域URL的操作必须在startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()之间完成,包括读取、复制、移动等操作。
  • 对于File Provider文件,建议将复制和分析操作放在后台队列执行,避免阻塞主线程。
  • 必须处理startAccessingSecurityScopedResource()返回false的情况,及时向用户提示权限获取失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 13:40:56