自定义URLProtocol下uploadTask代理进度方法未触发问题
自定义URLProtocol模拟上传:进度代理未触发的原因与解决
自定义URLProtocol完全支持模拟上传场景,你遇到的didSendBodyData进度代理未触发的问题,核心原因是:上传进度回调需要URLProtocol主动向URLSession报告数据发送状态,而你的实现里缺少了这部分逻辑。
问题根源
- 下载场景中,URLProtocol通过分块调用
sendData(_:)返回数据时,URLSession会自动触发下载进度代理; - 但上传的
didSendBodyData回调是被动触发的——只有当URLProtocol通过URLProtocolClient主动通知已发送的Body数据量时,URLSession才会调用这个代理方法。如果你的实现直接跳过分块发送逻辑,一次性完成任务,就只会触发didCompleteWithError,不会有进度更新。
解决步骤
要触发上传进度代理,需要在自定义URLProtocol中模拟真实上传的分块发送流程:
- 获取上传Body数据:从请求的
httpBody或httpBodyStream中读取待上传的内容; - 分块发送并报告进度:将Body拆分成小块,通过
urlProtocol(_:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)方法主动通知URLSession当前进度; - 模拟网络延迟:用GCD或定时器模拟真实网络的传输间隔,避免一次性发送所有数据。
示例代码(核心逻辑)
在自定义URLProtocol的startLoading方法中添加上传处理逻辑:
override func startLoading() { guard let client = client else { return } // 仅处理上传请求(示例以POST为例) guard let requestBody = request.httpBody, request.httpMethod?.uppercased() == "POST" else { // 非上传请求的默认处理 let response = HTTPURLResponse(url: self.request.url!, statusCode: 200, httpVersion: "1.1", headerFields: nil)! client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client.urlProtocolDidFinishLoading(self) return } let totalBytes = requestBody.count var sentBytes = 0 let chunkSize = 1024 // 模拟每次发送1KB数据 // 后台线程模拟分块上传 DispatchQueue.global().async { while sentBytes < totalBytes { let endIndex = min(sentBytes + chunkSize, totalBytes) let currentChunkSize = endIndex - sentBytes // 主动报告上传进度 client.urlProtocol(self, didSendBodyData: currentChunkSize, totalBytesSent: endIndex, totalBytesExpectedToSend: totalBytes) sentBytes = endIndex Thread.sleep(forTimeInterval: 0.01) // 模拟网络传输延迟 } // 上传完成,返回响应 let response = HTTPURLResponse(url: self.request.url!, statusCode: 200, httpVersion: "1.1", headerFields: nil)! client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) client.urlProtocolDidFinishLoading(self) } }
额外注意点
- 如果上传使用的是
httpBodyStream,需要先读取流中的完整数据再分块,不能直接用httpBody; - 确保
totalBytesExpectedToSend设置为真实的Body总大小,否则进度计算会出现偏差; - 单元测试中使用
XCTestExpectation时,要等待所有分块发送完成后再调用fulfill(),避免测试提前结束。
内容的提问来源于stack exchange,提问作者horseshoe7
相关产品推荐
相关产品推荐

