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

iOS 11中URLSessionDataTask进度显示异常问题求助

解决DataTask上传进度获取的问题

这个问题我之前也碰到过,核心原因是URLSessionDataTask的progress属性默认跟踪的是响应数据的下载进度,而非请求体的上传进度——这就是你看到进度从0.95开始的原因,那时候其实已经在接收服务器响应了。

既然不能用uploadTask,也不能用委托初始化会话,我们可以通过自定义HTTPBody流来手动统计上传字节,这是最可靠的方案,具体实现如下:

步骤1:实现进度跟踪的自定义InputStream

我们包装一个原生的InputStream,在它的read方法中统计已发送的字节数(因为URLSession会通过这个方法读取请求体数据并发送):

import Foundation

class ProgressTrackingInputStream: NSInputStream {
    private let wrappedStream: NSInputStream
    private(set) var totalBytes: Int64
    private(set) var bytesSent: Int64 = 0
    /// 进度回调,参数是0~1的进度值
    var progressHandler: ((Double) -> Void)?
    
    // 初始化方法1:传入Data
    init(data: Data) {
        self.wrappedStream = NSInputStream(data: data)
        self.totalBytes = Int64(data.count)
        super.init(data: data)
    }
    
    // 初始化方法2:传入文件URL(适合大文件上传,避免内存占用过高)
    init(fileURL: URL) throws {
        self.wrappedStream = NSInputStream(url: fileURL)!
        let fileAttributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
        self.totalBytes = fileAttributes[.size] as! Int64
        super.init(data: Data())
    }
    
    // 转发InputStream的核心方法
    override func open() {
        wrappedStream.open()
    }
    
    override func close() {
        wrappedStream.close()
    }
    
    override func read(_ buffer: UnsafeMutablePointer<UInt8>, maxLength len: Int) -> Int {
        let bytesRead = wrappedStream.read(buffer, maxLength: len)
        if bytesRead > 0 {
            bytesSent += Int64(bytesRead)
            let progress = Double(bytesSent) / Double(totalBytes)
            // 回到主线程更新UI(如果需要的话)
            DispatchQueue.main.async {
                self.progressHandler?(progress)
            }
        }
        return bytesRead
    }
    
    // 转发其他必要属性和方法
    override var hasBytesAvailable: Bool {
        wrappedStream.hasBytesAvailable
    }
    
    override var streamError: Error? {
        wrappedStream.streamError
    }
    
    override var streamStatus: Stream.Status {
        wrappedStream.streamStatus
    }
    
    override func property(forKey key: NSStream.PropertyKey) -> Any? {
        wrappedStream.property(forKey: key)
    }
    
    override func setProperty(_ property: Any?, forKey key: NSStream.PropertyKey) -> Bool {
        wrappedStream.setProperty(property, forKey: key)
    }
}

步骤2:使用自定义流创建DataTask

把自定义的InputStream设置为请求的httpBodyStream,然后正常创建并启动dataTask:

// 1. 准备上传数据(或文件URL)
let uploadData = "你的上传内容".data(using: .utf8)!
// 如果是大文件,用这个:
// let fileURL = URL(fileURLWithPath: "/path/to/your/file")
// let trackingStream = try! ProgressTrackingInputStream(fileURL: fileURL)

// 2. 创建请求
let uploadURL = URL(string: "https://your-upload-api.com")!
var request = URLRequest(url: uploadURL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

// 3. 初始化进度跟踪流
let trackingStream = ProgressTrackingInputStream(data: uploadData)
trackingStream.progressHandler = { progress in
    print("上传进度:\(String(format: "%.2f%%", progress * 100))")
    // 这里可以更新UI,比如进度条的progress属性
}

// 4. 设置请求体流并启动任务
request.httpBodyStream = trackingStream
let session = URLSession.shared // 符合你不能用委托初始化会话的要求
let task = session.dataTask(with: request) { data, response, error in
    // 处理上传完成后的响应
    if let error = error {
        print("上传失败:\(error.localizedDescription)")
        return
    }
    print("上传成功")
}
task.resume()

为什么这个方案可行?

当URLSession发送POST请求时,会不断调用httpBodyStream的read方法来获取要发送的数据,我们通过在这个方法中累计读取的字节数,就能准确计算出上传的进度——完全不受默认progress属性的干扰,而且不需要依赖委托或uploadTask。

内容的提问来源于stack exchange,提问作者user1165756

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:10:05