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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 23:24:03