从端点接收大文件的正确实现方法 解决内存耗尽与404报错
问题根因梳理
- 你当前遇到的
Allowed memory size of 134217728 bytes exhausted tried to allocate 98570240 bytes报错是服务端PHP程序抛出的错误,不是iOS客户端的内存溢出:从响应头的X-Powered-By: PHP/7.3.24可以判断,服务端在生成、输出大文件时,一次性把全量文件内容读入了PHP进程内存,触达了128M的内存上限才抛出错误。 - 你用Alamofire流式请求拿到的404状态码,说明当前请求的URL路由不匹配:要么是路径拼写错误,要么是URL里的加密参数过期失效,没有匹配到对应服务接口。
大体积文件接收的正确实现方法
选型参考
- 纯大文件存储下载场景:优先用原生
URLSession.downloadTask,系统会自动把接收的分块数据写入临时文件,全程不会把全量内容加载进内存,资源占用最低。 - 需要边接收边处理数据(比如实时解密、动态解析)的场景:用Alamofire的
streamRequest,只要正确配置分块写入逻辑,不会出现内存暴涨问题。
Alamofire 流式请求的正确写法
你之前的代码只打印了流事件,没有做分块持久化,正确逻辑要做到:每收到一小块数据就直接写入本地文件,不要在内存中拼接全量数据。示例代码:
// 提前定义本地文件存储路径 let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let fileURL = documentsDir.appendingPathComponent("downloaded_report") // 清理旧的残留文件 if FileManager.default.fileExists(atPath: fileURL.path) { try? FileManager.default.removeItem(at: fileURL) } // 发起流式请求 AF.streamRequest(validURLString) .responseStream { responseStream in switch responseStream.event { // 处理每一段接收到的分块数据 case .stream(let chunkResult): switch chunkResult { case .success(let dataChunk): // 打开文件句柄,把分块数据追加写入磁盘,不做内存拼接 if let fileHandle = try? FileHandle(forWritingTo: fileURL) { fileHandle.seekToEndOfFile() fileHandle.write(dataChunk) try? fileHandle.close() } case .failure(let error): print("分块接收失败:\(error.localizedDescription)") } // 处理请求完成事件 case .complete(let completion): let statusCode = completion.response?.statusCode ?? 0 print("请求结束,响应状态码:\(statusCode)") } }
服务端配套调整(解决PHP内存溢出)
这个报错不是客户端能单方面修复的,需要服务端配合调整:
- 不要在PHP代码里用
file_get_contents()一次性读入大文件再输出,改用fpassthru()分块读取文件直接输出到响应流,降低内存占用。 - 对大文件下载接口,直接用Nginx的
X-Accel-Redirect指令把文件下载转交给Nginx处理,完全绕开PHP的执行流程,不会触发PHP的内存限制。 - 流式响应时把响应头的
Transfer-Encoding设置为chunked,不要用Identity编码一次性返回全量内容。
常见踩坑提醒
- 不要用普通的
AF.request方法接收大文件:这个方法默认会把所有响应数据加载到内存的Data实例中,文件体积稍大就会触发内存告警。 - 流式接收时不要把所有分块数据拼接成一个全局的
Data对象,这种写法和普通GET请求没有区别,内存会随文件体积同步上涨。 - 先解决404问题:核对请求路径和接口文档是否一致,同时检查URL里的加密参数是否过期,避免因为路由匹配失败拿到错误响应。
内容的提问来源于stack exchange,提问作者Jide Jacobs
相关产品推荐
相关产品推荐

