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

