使用URLSession downloadTask下载大文件完成时崩溃问题求助
问题场景
在iPhone mini上下载大于2GB的MP4文件时,下载过程内存保持稳定,但完成瞬间内存骤增导致应用崩溃;同款操作在iPhone 15上可正常运行。即使注释掉FileManager.default.moveItem(at:to:)代码,仍会出现内存不足问题,且下载完成的completionHandler已正常触发(执行了print("File downloaded successfully!"))。相关代码如下:
private func downloadFile(from url: URL, name: String, progressHandler: @escaping (Double) -> Void, completionHandler: @escaping (URL?, Error?) -> Void) { let task = URLSession.shared.downloadTask(with: url) { location, response, error in if let error = error { print("Error downloading file:", error) completionHandler(nil, error) return } guard let location = location else { print("Download location not available.") completionHandler(nil, nil) return } guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { print("Documents directory not found.") return } let destinationURL = documentsDirectory.appendingPathComponent("\(name).mp4") do { // try FileManager.default.moveItem(at: location, to: destinationURL) print("File downloaded successfully!") completionHandler(location, nil) } catch { print("Error moving downloaded file:", error) completionHandler(nil, error) } } let downloadProgress = task.progress let progressQueue = DispatchQueue(label: "com.example.progressQueue", qos: .utility, attributes: .concurrent) let progress = Progress(totalUnitCount: 100) progress.completedUnitCount = 0 let progressTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in progressQueue.async { progress.completedUnitCount = Int64(downloadProgress.fractionCompleted * 100) progressHandler(progress.fractionCompleted) } if progress.isFinished { timer.invalidate() } } task.resume() }
解决思路
- 排查临时文件的隐式读取:URLSession下载的临时文件(location指向路径),若completionHandler的调用方存在意外读取整个文件到内存的逻辑(比如转存、解析元数据时一次性加载全部数据),会触发内存暴增。iPhone mini内存更小,更容易达到崩溃阈值。检查调用方逻辑,改用流式处理或分块读取替代全量加载。
- 修正进度监听的资源泄漏问题:当前Timer轮询的方式可能导致
downloadProgress、task等资源无法及时释放,改用Progress的观察者模式替代Timer,同时使用弱引用避免循环引用:downloadProgress.addObserver(forKeyPath: "fractionCompleted", options: [.new], queue: progressQueue) { [weak self] progress, change in guard let self = self else { return } let completed = change.newValue as? Double ?? 0 progressHandler(completed) if progress.isFinished { progress.removeObserver(self, forKeyPath: "fractionCompleted") } } - 手动清理临时文件:在completionHandler处理完location后,若无需保留临时文件,调用
try? FileManager.default.removeItem(at: location),让系统及时回收临时文件占用的资源,避免后台预读文件内容到内存。 - 调整URLSession配置:自定义会话配置禁用缓存,减少额外内存占用:
let configuration = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nil let session = URLSession(configuration: configuration) - 内存快照定位问题:使用Xcode的Memory Graph Debugger拍摄下载完成时的内存快照,定位暴增的对象类型——若为Data对象则指向全量文件读取逻辑,若为其他对象则排查循环引用或未释放资源。同时在completionHandler中加入内存打印:
let memoryMB = ProcessInfo.processInfo.physicalMemory / 1024 / 1024 print("Post-download memory usage: \(memoryMB) MB")
内容的提问来源于stack exchange,提问作者Joan Cardona
相关产品推荐
相关产品推荐

