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

iOS下载文件时如何直接保存到指定路径以便边下载边读取

实现方案

你当前使用的URLSession.shared.dataTask带尾随闭包的写法,只会在下载全部完成后一次性返回所有数据,自然没法拿到实时分片数据。要实现边下载边直接写入目标路径,需要自定义URLSession并设置代理,遵循URLSessionDataDelegate协议接收每次返回的数据包即可。

1. 声明必要属性

首先在你实现功能的类中声明需要用到的属性:

// 最终存储的目标文件路径,示例存在documents目录下,你可按需修改
private let targetFileURL: URL = {
    let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    return docDir.appendingPathComponent("audiofile.ogg")
}()
// 用于实时写入文件的句柄
private var fileHandle: FileHandle?
// 下载任务实例
private var downloadTask: URLSessionDataTask?
// 进度观察器,需要展示下载进度可保留
private var observation: NSKeyValueObservation?

2. 初始化自定义Session启动下载

不要使用默认的shared session,自定义session并绑定代理:

func downloadAudioFile() {
    // 目标路径有旧文件先删除,避免内容拼接错乱
    if FileManager.default.fileExists(atPath: targetFileURL.path) {
        try? FileManager.default.removeItem(at: targetFileURL)
    }
    // 先创建空的目标文件,后续往里面追加下载数据
    FileManager.default.createFile(atPath: targetFileURL.path, contents: nil)
    // 初始化文件写入句柄
    fileHandle = try? FileHandle(forWritingTo: targetFileURL)
    
    // 自定义URLSession,设置代理为当前类
    let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
    let url = URL(string: "https://www.demo.com/audiofile.ogg")!
    downloadTask = session.dataTask(with: url)
    
    // 原有进度观察逻辑可保留
    observation = downloadTask?.progress.observe(\.fractionCompleted, changeHandler: { progress, _ in
        print("下载进度: \(progress.fractionCompleted)")
    })
    
    downloadTask?.resume()
}

3. 实现代理方法实时写入数据

扩展当前类,遵循URLSessionDataDelegate协议,实现接收数据、下载完成的回调方法:

// 注意把YourViewController替换为你当前实现功能的类名
extension YourViewController: URLSessionDataDelegate {
    // 每次收到新的数据包都会触发该方法
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        guard let fileHandle = fileHandle else { return }
        // 移动句柄到文件末尾,追加新收到的数据
        fileHandle.seekToEndOfFile()
        fileHandle.write(data)
        
        // 此处已经可以拿到完整的目标文件路径targetFileURL,直接传给播放器即可启动流式播放,无需等下载完成
    }
    
    // 下载完成/失败的最终回调
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        // 关闭文件句柄释放资源
        try? fileHandle?.close()
        fileHandle = nil
        
        if let error = error {
            print("下载失败: \(error.localizedDescription)")
            // 下载失败可按需删除不完整的文件
            try? FileManager.default.removeItem(at: targetFileURL)
        } else {
            print("下载完成,文件路径: \(targetFileURL.path)")
        }
        // 销毁session避免内存泄漏
        session.finishTasksAndInvalidate()
    }
}

注意事项

  • 该实现完全跳过了临时文件步骤,所有下载数据直接写入你指定的最终路径,符合需求
  • 若要实现边下边播,注意播放器的缓冲逻辑,如果使用AVPlayer遇到播放卡顿的情况,可以手动调用player.seek(to: currentTime)触发刷新文件长度
  • 只要操作的是应用沙盒内的路径,不需要额外申请读写权限

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.13 15:21:01