NSURLSessionDownloadTask 进度回调不顺畅?

Posted

技术标签:

【中文标题】NSURLSessionDownloadTask 进度回调不顺畅?【英文标题】:NSURLSessionDownloadTask progress callback is not smoothly? 【发布时间】:2017-03-08 15:50:55 【问题描述】:

我在前台通过NSURLSessionDownloadTask下载了一个文件,我需要在UI中显示当前的下载速度,

但我发现委托方法:URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 调用不顺畅:

这样我可以显示准确的当前下载速度。

我确定这不是由服务器引起的。当我通过 ASIHTTPRequest 下载相同的文件时,没关系,进度块被顺利调用。

有人知道为什么吗?谢谢!

更新 #0:

委托方法的实现:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 
    _totalReceived += bytesWritten;
    NSLog(@"receive: %8.2f KB, total receive: %8.2f KB", bytesWritten / 1024.f,  _totalReceived / 1024.f);
    CFTimeInterval now = CFAbsoluteTimeGetCurrent();
    if (_lastDataReceiveAt > 0.f) 
        CFTimeInterval delta = now - _lastDataReceiveAt;
        NSLog(@"downloadtask: %p, speed: %8.2f KB/s; avg: %8.2f KB/s; delta time: %3.2fs",
              downloadTask,
              bytesWritten / 1024 / delta, _totalReceived / 1024 / (now - _beginsAt),
              delta);
    
    _lastDataReceiveAt = now;

    //NSLog(@"downloading ... %.2f%%", totalBytesWritten * 100.f / totalBytesExpectedToWrite);

更新 #1:

嗨,@Rob,这是我的会话安装代码:

- (NSURLSession *)sessionWithIdentifier:(NSString *)identifier 
    if (identifier == nil) 
        NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
        identifier = [NSString stringWithFormat:@"%@:///Downloader#%ld", bundleId, time(NULL)];
    

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    //config.protocolClasses = @[ASIURLProtocol.class];
    return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];

并且,下载任务初始化:

_session = [self sessionWithIdentifier:nil];
NSURL *url = ...;
_task = [_session downloadTaskWithURL:url];
[_task resume];

CFTimeInterval now = CFAbsoluteTimeGetCurrent();
_beginsAt = now;
_lastDataReceiveAt = now;
_totalReceived = 0;

【问题讨论】:

我测试了一些明显的可能性(会话队列的优先级;并发队列 v 串行队列;下载任务 v 数据任务;确保应用程序已达到静止状态;等等),但我没有发现任何问题。与URLSession 的通话只有一些情节延迟。如果 ASIHTTPRequest 没有受此影响,那可能只是 URLSession 和使用的任何 ASIHTTPRequest 之间的差异。我只是建议坚持使用URLSession 并使用一些尾随平均值或类似的东西,而不是依赖一个数据点来计算速度。 虽然我们在NSURLSession 委托方法中看到了一些偶发延迟,但通常以毫秒为单位,而不是 10 秒 (!)。我注意到您将主队列用于委托队列,如果您曾经阻塞主线程,您会看到您描述的那种行为。我建议将队列参数更改为nil,看看这 10 秒的延迟是否会消失。但是,如果是这样,您确实应该返回并确定阻塞主线程的原因,因为它永远不应该被阻塞。 【参考方案1】:

这里的顺利是什么意思?他们不是以相同的时间间隔被调用吗?这是意料之中的。

您需要做的就是跟踪开始时间,并在回调中根据经过的时间和收到的数据计算速度。

如果这不是问题,请提供更多代码,也许是委托方法实现以及产生这些日志的原因。也许您还希望日志是什么?

【讨论】:

或者您可以只使用最近一次通话中收到的数据大小,以及自上次通话以来经过的时间,并使用它来确定速度。这将解决这个问题。 这不是他在做的吗?问题是一些进度更新有一点点延迟,所以当他只看最后一个数据点时,看起来速度波动很大。我建议介于“查看最后一个数据点”和“平均所有数据点”之间... 啊,对不起,我明白了。您可能想尝试一下最适合您的界面的方法,但有一些想法……您可以尝试一个简单的滑动窗口样式并查看最后的 X 块并在这些块上取平均值。或者,一个更好的主意是计算所有块的加权平均值。您可以为最近的块分配较低的权重,这样您仍然可以获得一个代表值,但最好考虑到速度变化的新近度。 @Rob 是的,这就是我想做的。但是委托方法不能称为“顺利”,一批数据在很短的时间内到达,然后,在很长一段时间(10秒?)之后,下一批数据在很短的时间内到达。所以速度波的形状突然上升,然后慢慢下降,然后又突然上升,周期性地。 @AlexLee - 10 秒有点极端,但是您的代码向我们展示了您在每次调用 didWriteData 时计算的速度,我建议您在每次调用时计算速度,但是然后平均,例如,跟踪平均值中的最后 5 或 6 个,以使其专注于最近的速度,而不是仅仅依赖于最后一次调用。

以上是关于NSURLSessionDownloadTask 进度回调不顺畅?的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSessionDownloadTask 进度回调不顺畅?

在 NSMutableDictionary 中设置 NSURLSessionDownloadTask

NSUrlSessionDownloadTask - 进入后台时出现didCompleteWithError

iOS开发之网络编程--2NSURLSessionDownloadTask文件下载

NSURLSessionDownloadTask 在挂起时继续下载

在下载字节时从 NSURLSessionDownloadTask 访问字节