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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 21:56:04