使用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
相关产品推荐
相关产品推荐

