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

自定义URLProtocol下uploadTask代理进度方法未触发问题

自定义URLProtocol模拟上传:进度代理未触发的原因与解决

自定义URLProtocol完全支持模拟上传场景,你遇到的didSendBodyData进度代理未触发的问题,核心原因是:上传进度回调需要URLProtocol主动向URLSession报告数据发送状态,而你的实现里缺少了这部分逻辑。

问题根源

  • 下载场景中,URLProtocol通过分块调用sendData(_:)返回数据时,URLSession会自动触发下载进度代理;
  • 但上传的didSendBodyData回调是被动触发的——只有当URLProtocol通过URLProtocolClient主动通知已发送的Body数据量时,URLSession才会调用这个代理方法。如果你的实现直接跳过分块发送逻辑,一次性完成任务,就只会触发didCompleteWithError,不会有进度更新。

解决步骤

要触发上传进度代理,需要在自定义URLProtocol中模拟真实上传的分块发送流程:

  1. 获取上传Body数据:从请求的httpBody或httpBodyStream中读取待上传的内容;
  2. 分块发送并报告进度:将Body拆分成小块,通过urlProtocol(_:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)方法主动通知URLSession当前进度;
  3. 模拟网络延迟:用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 13:27:15