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

Swift对接Firebase Storage上传多类型文件报错排查

问题原因

第二次上传触发POSIX 40错误、上传失败,是代码里三个明显问题导致的,和Firebase服务端无关:

  • 选中文件后没有手动关闭文档选择器。UIDocumentPickerViewController不会在用户选完文件后自动dismiss,第一次选完后选择器一直残留在视图栈中,第二次触发上传时会出现视图层级冲突,连带破坏文件访问的权限链路。
  • 直接使用系统返回的临时URL上传。.import模式下系统返回的URL指向App临时tmp目录,系统会不定期自动清理该目录下的文件,且该URL的访问权限只在pick回调的当前生命周期内有效,第一次上传时权限和文件都存在所以能成功,后续再访问就会因为权限失效/文件被清理报错。
  • 没有对上传任务做强引用。putFile生成的上传任务如果没有被属性持有,会在回调方法执行结束后被ARC自动回收,第一次上传可能因为执行速度快刚好跑完,第二次如果遇到内存紧张、文件稍大,任务中途被回收就会直接报网络/链路错误。
  • 额外逻辑漏洞:你开了多选支持但永远只取urls.first,没有做空值兜底,用户选多个文件时很容易拿到无效路径。
修复方案

直接替换对应代码即可:

  1. 先在类里新增两个必要属性
// 持有当前上传任务,避免被系统回收
var currentUploadTask: StorageUploadTask?
// 自定义上传临时目录路径,自己管理文件避免被系统清理
let uploadTempDir: URL = {
    let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
    let tempDir = cacheDir.appendingPathComponent("CustomUploadTemp", isDirectory: true)
    if !FileManager.default.fileExists(atPath: tempDir.path) {
        try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
    }
    return tempDir
}()
  1. 替换原有的didPickDocumentsAt代理实现
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    // 先关闭选择器,避免视图栈残留
    controller.dismiss(animated: true)
    
    guard let selectedURL = urls.first else { return }
    let fileManager = FileManager.default
    
    // 把系统返回的临时文件拷贝到自己管理的目录,永久持有访问权限
    let targetFileURL = uploadTempDir.appendingPathComponent(selectedURL.lastPathComponent)
    // 存在同名旧文件先删除
    if fileManager.fileExists(atPath: targetFileURL.path) {
        try? fileManager.removeItem(at: targetFileURL)
    }
    
    do {
        try fileManager.copyItem(at: selectedURL, to: targetFileURL)
    } catch {
        print("文件预处理失败:\(error)")
        return
    }
    
    // 用可控的本地路径上传
    let fileRef = storage.reference()
        .child("Customer Name")
        .child(selectedURL.deletingPathExtension().lastPathComponent)
    
    currentUploadTask = fileRef.putFile(from: targetFileURL, metadata: nil) { _, error in
        // 上传结束不管成功失败,删除本地临时拷贝释放空间
        try? fileManager.removeItem(at: targetFileURL)
        if let error = error {
            print("上传失败:\(error)")
            return
        }
        print("上传成功")
    }
}
补充说明
  • 要实现多文件上传的话,遍历urls数组,逐个执行拷贝、上传逻辑即可,不要只取第一个元素。
  • 文件名里的空格、括号、中文不需要手动转义,Firebase SDK会自动处理。
  • 如果要支持后台上传大体积视频,记得在AppDelegate中实现背景URL会话回调,将系统提供的completionHandler传递给Firebase Storage,避免后台任务被系统挂起。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 22:30:57