iOS11新增NSURLSessionTask的progress属性为何失效?
哎,这个坑我当年在iOS11刚发布的时候可是踩得明明白白!你遇到的问题完全是系统这个新属性的设计缺陷导致的——iOS11给NSURLSessionTask加的progress属性,其实是统计整个任务的所有网络传输字节数,包括HTTP请求头、响应头,甚至重定向时的额外数据,而不是单纯的你要下载的文件内容大小。这就导致它的数值完全和实际下载进度对不上,比如你看到的78万字节下载了但进度显示0,就是因为系统把这些字节算成了“额外传输数据”,没算到下载内容的进度里;任务完成时的5字节更是离谱,大概率是最后收尾的一些响应数据被它当成了总进度。
那iOS11上要做可靠的UI进度展示,答案是肯定的:还是得自己写逻辑,别依赖系统的progress属性。最靠谱的方式还是用传统的下载代理方法来计算进度,这也是一直以来的稳定方案:
推荐实现方式
通过NSURLSessionDownloadDelegate的代理方法(如果是数据任务就用NSURLSessionDataDelegate的对应方法),直接拿到下载内容的真实字节数来计算:
Objective-C 示例
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { // 排除未知总大小的情况 if (totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown) { CGFloat progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite; // 切回主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ self.downloadProgressView.progress = progress; }); } }
Swift 示例
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { guard totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown else { return } let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite) // 主线程更新UI DispatchQueue.main.async { self.downloadProgressView.progress = progress } }
为什么不推荐用系统的progress?
除了你遇到的数值混乱问题,iOS11的这个属性还有个坑:当任务遇到重定向或者服务器返回的Content-Length有变化时,progress的totalUnitCount会被重置,导致进度突然跳变或者归零,完全没法给用户展示稳定的进度。这个问题直到iOS12之后才得到部分修复,所以在iOS11上完全不能指望它。
总结一下:iOS11的NSURLSessionTask.progress就是个半成品,做UI进度展示还是老老实实用代理方法自己计算最稳妥,既能保证准确性,也不会出现莫名其妙的bug。
内容的提问来源于stack exchange,提问作者Mojo66

