如何在Alamofire中通过Multipart Form上传大文件?
问题描述
尝试通过POST请求的Multipart Form方式上传两张图片(10MB和1MB),使用Alamofire的upload方法实现,代码如下:
AF.upload(multipartFormData: { multipartFormData in multipartFormData.append(InputStream(data: imageMedia.data), // 10MB data withLength: UInt64(imageMedia.data.count), name: imageMedia.type.rawValue, fileName: imageMedia.filename, mimeType: imageMedia.mimeType) multipartFormData.append(InputStream(data: maskMedia.data), // 1MB data withLength: UInt64(maskMedia.data.count), name: maskMedia.type.rawValue, fileName: maskMedia.filename, mimeType: maskMedia.mimeType) }, with: request).responseData { afDataResponse in switch afDataResponse.result { case .success(let data): completion(data) case .failure(let error): print("error with alamofire response \(error.localizedDescription)") completion(nil) } }
请求执行失败,报错信息:
error with alamofire response: URLSessionTask failed with error: The operation couldn’t be completed. Message too long
补充信息:
- 相同请求在Postman中可正常执行,排除服务器问题
- 使用较小图片(5MB+1MB)时上传正常
详细错误日志:
sessionTaskFailed(error: Error Domain=NSPOSIXErrorDomain Code=40 "Message too long"
UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalUploadTask <8F7404DD-2947-4B18-9F23-C23F0483F440>.<1>,
_kCFStreamErrorDomainKey=1, NSErrorPeerAddressKey=<CFData 0x600001068280 [0x1b9553c60]>{length = 16, capacity = 16, bytes =
0x100201bbd8ef26350000000000000000}, _kCFStreamErrorCodeKey=40,
_NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalUploadTask <8F7404DD-2947-4B18-9F23-C23F0483F440>.<1>")})
问题原因
iOS的URLSession默认会将整个请求体加载到内存中处理,当请求体大小超过系统阈值(通常约8MB)时,就会触发Message too long错误。Postman会自动启用分块上传,而Alamofire默认配置未针对性处理大文件场景。
解决方案
1. 优先采用文件URL直接上传(最优解)
避免将图片数据加载到内存,直接使用本地文件URL构建表单,Alamofire会自动采用流式上传,不占用大量内存:
// 假设imageMedia和maskMedia持有图片的本地文件URL AF.upload(multipartFormData: { multipartFormData in if let imageFileURL = imageMedia.fileURL { multipartFormData.append(imageFileURL, withName: imageMedia.type.rawValue, fileName: imageMedia.filename, mimeType: imageMedia.mimeType) } if let maskFileURL = maskMedia.fileURL { multipartFormData.append(maskFileURL, withName: maskMedia.type.rawValue, fileName: maskMedia.filename, mimeType: maskMedia.mimeType) } }, with: request) .responseData { afDataResponse in switch afDataResponse.result { case .success(let data): completion(data) case .failure(let error): print("error with alamofire response \(error.localizedDescription)") completion(nil) } }
2. 启用分块传输编码
修改请求配置,强制开启HTTP分块传输,让请求体分块发送,避免一次性加载到内存:
var request = request // 原请求实例 request.setValue("chunked", forHTTPHeaderField: "Transfer-Encoding") AF.upload(multipartFormData: { multipartFormData in // 原表单构建代码 }, with: request) .responseData { afDataResponse in // 原响应处理代码 }
3. 调整Session内存阈值
如果必须使用内存数据,可以修改Alamofire Session的配置,增大请求体内存阈值,超过阈值时自动写入磁盘处理:
let configuration = URLSessionConfiguration.default // 设置内存阈值为20MB,超过则写入磁盘 configuration.httpMaximumConnectionsPerHost = 5 configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nil let session = Session(configuration: configuration) session.upload(multipartFormData: { multipartFormData in // 原表单构建代码 }, with: request) .responseData { afDataResponse in // 原响应处理代码 }
验证建议
优先测试第一种方案(文件URL上传),该方案稳定性最高且内存占用最低,可直接解决大文件上传的内存溢出问题。
内容的提问来源于stack exchange,提问作者DanielZanchi

