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
相关产品推荐
相关产品推荐

