Swift POST上传大量图片如何避免将全量图片加载到内存
核心问题原因
你现在遇到的内存溢出是三个原因共同导致的:
- 一次性加载所有500张高清图片的
Data到数组,单张2K分辨率的图片压缩后大小约2MB,500张直接占用1GB左右内存,远超普通iOS应用的可用内存阈值 - JSON编码时会把所有二进制
Data转成Base64字符串,体积额外增加33%,且编码过程中会同时存在原始Data数组和编码后的完整HTTP Body,内存占用直接翻倍 - URLSession dataTask会把完整的HTTP Body缓存在内存中发送,进一步推高内存占用
解决方案
1. 存储层优化:图片落地到沙盒,CoreData只存索引
CoreData不适合存储大容量二进制数据,调整你的SaveData结构:
- 每次用
snapshot()拿到截图后,直接压缩为JPG格式写入沙盒临时目录,文件名按序号命名即可,比如frame_0.jpg SaveData里只存储图片文件路径数组和dataList数组,完全不存储图片二进制数据,CoreData读写内存占用可以忽略
2. 传输层优化:改用multipart/form-data流式上传
放弃用JSON格式承载所有图片,改用multipart/form-data格式实现流式上传,不需要把所有图片加载到内存,边读本地文件边发送,内存占用始终只有单张图片的大小,且服务端改造成本极低,几乎所有后端框架都原生支持multipart格式解析。
首先替换请求生成逻辑,不需要传入[Data]数组,传入本地图片路径数组即可:
private func generateStreamPostRequest(framePaths: [String], dataList: [[Int]]) -> URLRequest { let boundary = UUID().uuidString var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") // 自定义分块输入流,边读文件边生成请求内容,不会全量加载到内存 let inputStream = MultipartInputStream(boundary: boundary, framePaths: framePaths, dataList: dataList) request.httpBodyStream = inputStream // 可选:提前计算请求总大小设置Content-Length,避免chunked编码 request.setValue("\(inputStream.contentLength)", forHTTPHeaderField: "Content-Length") return request }
你需要自行实现MultipartInputStream子类(继承InputStream),核心逻辑为:先发送dataList的JSON分块,再遍历图片路径逐张读取文件流发送,读完一张就释放对应内存,全部发送完成后追加结束分隔符即可。
3. 替换请求类型:用上传任务替代dataTask
dataTask会全量缓存请求体到内存,上传任务原生支持从流/文件读取数据,内存占用极低:
func postToServer(framePaths: [String], dataList: [[Int]], _ sender: ViewController, completion: @escaping([UIImage])->Void) { let request = generateStreamPostRequest(framePaths: framePaths, dataList: dataList) // 用流式上传任务,不需要全量加载请求体到内存 URLSession.shared.uploadTask(withStreamedRequest: request) { data, response, error in // 原有回调逻辑完全复用即可 if error != nil { DispatchQueue.main.async { //sender.videoError(title: "无法处理视频", message: "网络连接失败", presentVideo: false) } return } guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { DispatchQueue.main.async { //sender.videoError(title: "无法处理视频", message: "服务端响应异常,状态码:\((response as! HTTPURLResponse).statusCode)", presentVideo: false) } return } guard let data=data else{ print("响应无数据") return } if let decoded=try? JSONDecoder().decode(DISCRequest.self, from: data){ completion(decoded.images.map({ i in UIImage(data: i)! })) } }.resume() }
可选优化
- 如果服务端必须接收JSON格式请求:可以用流式JSON编码器,逐张读取图片编码写入临时文件,再用上传任务传输临时文件,同样不需要全量加载图片到内存
- 截图时选择中等质量JPG压缩,比PNG体积小70%以上,画质损失几乎可忽略
- 上传完成后记得删除沙盒内的临时图片文件,避免占用用户存储空间
内容的提问来源于stack exchange,提问作者Matt Pinz
相关产品推荐
相关产品推荐

