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

使用URLSessionDownloadTask下载PDF后无法打开,求排查方案

问题分析与解决方案

首先,你的这段下载代码确实存在几个可能导致PDF损坏的问题,我来逐一拆解并给出修复方案:

代码中的核心问题

  • 强制解包fileURL!:如果传入的urlString格式无效(比如拼写错误、缺少http/https前缀),这行代码会直接崩溃,而且你没有任何错误处理逻辑。更关键的是,如果URL无效,下载任务本身就会失败,但强制解包会提前掩盖真实问题。
  • 未验证HTTP响应状态:下载完成后你只检查了error == nil,但没确认服务器返回的是成功状态码(200-299区间)。如果服务器返回404、500等错误,你得到的会是错误页面的HTML内容而非正常PDF,自然无法打开。
  • 冗余的目录遍历:下载完成后遍历Documents目录找目标文件完全没必要——你已经明确知道destinationFileUrl的路径,直接用这个URL就行,遍历操作不仅多余,还可能因目录结构变化导致找不到文件。
  • 静默忽略删除失败:try? FileManager.default.removeItem(at: destinationFileUrl)会忽略删除操作的错误(比如文件不存在),如果目标文件已存在且被占用,删除失败后复制会报错,但你没有对应的反馈逻辑。

修复后的代码

static func downloadFileFromUrl(urlString: String, fileName: String, viewController: UIViewController) {
    // 1. 安全解析URL,避免强制解包
    guard let fileURL = URL(string: urlString) else {
        DispatchQueue.main.async {
            UIApplication.shared.keyWindow?.makeToast(message: "无效的文件URL")
        }
        return
    }
    
    // 2. 安全获取Documents目录路径
    guard let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
        DispatchQueue.main.async {
            UIApplication.shared.keyWindow?.makeToast(message: "无法访问Documents目录")
        }
        return
    }
    
    let destinationFileUrl = documentsUrl.appendingPathComponent(fileName)
    
    let sessionConfig = URLSessionConfiguration.default
    let session = URLSession(configuration: sessionConfig)
    let request = URLRequest(url: fileURL)
    
    let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
        // 3. 处理下载错误,给出具体提示
        if let error = error {
            DispatchQueue.main.async {
                UIApplication.shared.keyWindow?.makeToast(message: "下载失败:\(error.localizedDescription)")
            }
            return
        }
        
        // 4. 验证HTTP响应状态码,确保服务器返回有效文件
        guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
            DispatchQueue.main.async {
                UIApplication.shared.keyWindow?.makeToast(message: "服务器返回错误,无法下载文件")
            }
            return
        }
        
        // 5. 确保临时下载文件存在
        guard let tempLocalUrl = tempLocalUrl else {
            DispatchQueue.main.async {
                UIApplication.shared.keyWindow?.makeToast(message: "下载的文件无效")
            }
            return
        }
        
        // 6. 处理目标文件存在的情况,明确捕获删除错误
        do {
            if FileManager.default.fileExists(atPath: destinationFileUrl.path) {
                try FileManager.default.removeItem(at: destinationFileUrl)
            }
            try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
            
            // 7. 直接使用目标URL打开分享界面,无需遍历目录
            DispatchQueue.main.async {
                let activityViewController = UIActivityViewController(activityItems: [destinationFileUrl], applicationActivities: nil)
                viewController.present(activityViewController, animated: true, completion: nil)
            }
        } catch let writeError {
            DispatchQueue.main.async {
                UIApplication.shared.keyWindow?.makeToast(message: "保存文件失败:\(writeError.localizedDescription)")
            }
        }
    }
    task.resume()
}

额外排查步骤

除了修复代码,你还可以做这些事情定位问题:

  • 直接在浏览器中打开传入的urlString,确认能下载到正常PDF,且文件大小符合预期。
  • 下载完成后,通过Xcode的Devices and Simulators查看Documents目录下的PDF,导出到电脑尝试打开,确认是文件本身损坏还是打开方式问题。
  • 对比下载后文件和服务器原文件的大小,如果差异很大,说明下载过程中数据丢失或截断。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:06:13