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

使用Alamofire MultiParts上传图片时因内存问题崩溃求解决方案

解决Alamofire多图上传内存崩溃问题

哇,一次性处理36张80MB的原图确实很容易把内存撑爆——毕竟每张JPG转成UIImage位图后,内存占用可能是原文件的3-5倍(比如80MB的图转成RGBA格式就是80*4=320MB左右),36张加起来直接就破G了,系统不杀你杀谁😅。我来给你几个针对性的修复方案,从根源解决这个问题:

1. 最推荐:直接上传本地文件URL,完全不加载图片到内存

你现在的流程是PHAsset → UIImage → Data → 上传,这一步把整个图片都读到内存里了,完全没必要。其实可以直接获取PHAsset对应的本地文件URL,用Alamofire直接上传这个文件,内存占用几乎可以忽略。

修改步骤:

  • 先获取PHAsset的本地文件URL:
import Photos

func getLocalFileURL(for asset: PHAsset, completion: @escaping (URL?) -> Void) {
    let resources = PHAssetResource.assetResources(for: asset)
    guard let resource = resources.first else {
        completion(nil)
        return
    }
    
    let fileManager = FileManager.default
    let tempDir = fileManager.temporaryDirectory
    let fileName = resource.originalFilename
    let tempURL = tempDir.appendingPathComponent(fileName)
    
    // 如果临时文件已存在,直接返回
    if fileManager.fileExists(atPath: tempURL.path) {
        completion(tempURL)
        return
    }
    
    // 把Asset资源导出到临时文件
    let manager = PHAssetResourceManager.default()
    manager.writeData(for: resource, toFile: tempURL, options: nil) { error in
        if let error = error {
            print("导出文件失败:\(error.localizedDescription)")
            completion(nil)
        } else {
            completion(tempURL)
        }
    }
}
  • 然后用Alamofire直接上传这个URL,不用转成UIImage:
// 分批处理Asset,比如每次处理4个
func uploadAssetsInBatches(assets: [PHAsset], batchSize: Int = 4) {
    guard !assets.isEmpty else { return }
    let currentBatch = Array(assets.prefix(batchSize))
    let remainingAssets = Array(assets.suffix(from: batchSize))
    
    // 先获取当前批次所有文件URL
    let group = DispatchGroup()
    var fileURLs: [URL] = []
    
    for asset in currentBatch {
        group.enter()
        getLocalFileURL(for: asset) { url in
            if let url = url {
                fileURLs.append(url)
            }
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        // 构造MultipartFormData上传
        AF.upload(multipartFormData: { multipartFormData in
            for (index, url) in fileURLs.enumerated() {
                multipartFormData.append(url, withName: "images[\(index)]", fileName: url.lastPathComponent, mimeType: "image/jpeg")
            }
            // 其他参数也可以在这里append
        }, to: "你的上传接口URL")
        .responseJSON { response in
            switch response.result {
            case .success:
                print("当前批次上传完成")
                // 继续上传下一批
                self.uploadAssetsInBatches(assets: remainingAssets)
            case .failure(let error):
                print("上传失败:\(error.localizedDescription)")
                // 这里可以加重试逻辑
            }
            
            // 上传完成后删除临时文件
            fileURLs.forEach { url in
                try? FileManager.default.removeItem(at: url)
            }
        }
    }
}

2. 退而求其次:加载图片时降采样,避免加载原图

如果因为某些原因必须转成UIImage再上传,那一定要在getUIImageFromPHAsset里做降采样,不要加载原图。比如按服务器要求的最大尺寸来加载,比如1080P:

修改getUIImageFromPHAsset方法:

func getUIImageFromPHAsset(assets: [PHAsset], targetSize: CGSize = CGSize(width: 1080, height: 1920), completion: @escaping ([UIImage]) -> Void) {
    let group = DispatchGroup()
    var images: [UIImage] = []
    
    for asset in assets {
        group.enter()
        let options = PHImageRequestOptions()
        options.isNetworkAccessAllowed = true
        options.deliveryMode = .highQualityFormat
        options.resizeMode = .exact // 按目标尺寸缩放
        
        PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options) { image, info in
            if let image = image {
                images.append(image)
            }
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        completion(images)
    }
}
  • 同时,转成Data的时候也要压缩:
// 上传时压缩图片质量
if let imageData = image.jpegData(compressionQuality: 0.7) {
    multipartFormData.append(imageData, withName: "images[\(index)]", fileName: "image_\(index).jpg", mimeType: "image/jpeg")
}

3. 必须做:分批处理,不要一次性加载所有图片

不管用上面哪种方案,都不要一次性处理36张图,分成小批次(比如4-8张一批),上传完一批再处理下一批,这样内存里始终只有少量数据。上面的代码示例已经包含了分批逻辑,核心就是递归处理剩余的Asset数组。

4. 额外检查:避免循环引用和内存泄漏

检查你的completion闭包有没有用[weak self],防止循环引用导致内存无法释放:

self.getUIImageFromPHAsset(assets: self.phAssets) { [weak self] images in
    guard let self = self else { return }
    self.uploadingView.isHidden = false
    // 后续上传逻辑
}

按照上面的方案,优先用直接上传文件URL的方式,这是最省内存的,几乎不会出现内存问题。如果必须转UIImage,就结合降采样+分批上传,也能把内存控制在合理范围内。

内容的提问来源于stack exchange,提问作者Khoa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:53:02