NSURLSession中所有NSURLSessionTasks的平均进度

一个NSURLSession将允许你添加大量的NSURLSessionTask在后台下载。

如果你想检查一个NSURLSessionTask的进度,就像

double taskProgress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;

但是,检查NSURLSessionTasks中所有NSURLSessionTasks的平均进度的最佳方法是什么?

我想我会尝试平均所有任务的进展:

 [[self backgroundSession] getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *allDownloadTasks) { double totalProgress = 0.0; for (NSURLSessionDownloadTask *task in allDownloadTasks) { double taskProgress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive; if (task.countOfBytesExpectedToReceive > 0) { totalProgress = totalProgress + taskProgress; } NSLog(@"task %d: %.0f/%.0f - %.2f%%", task.taskIdentifier, (double)task.countOfBytesReceived, (double)task.countOfBytesExpectedToReceive, taskProgress*100); } double averageProgress = totalProgress / (double)allDownloadTasks.count; NSLog(@"total progress: %.2f, average progress: %f", totalProgress, averageProgress); NSLog(@" "); }]; 

但是这里的逻辑是错误的:假设你有50个任务需要下载1MB,3个任务需要下载100MB。 如果50个小任务在3个大任务之前完成, averageProgress将远远高于实际的平均进度。

因此,您必须根据TOTAL countOfBytesReceived除以TOTAL countOfBytesReceived计算平均进度。 但是问题是一个NSURLSessionTask 只会在启动时才 NSURLSessionTask算出这些值,并且在另一个任务完成之前它可能不会启动。

那么如何检查NSURLSessionTasks中所有NSURLSessionTasks的平均进度

在开始下载文件之前,您可以发送微小的HEAD requests来获取文件大小。 您只需添加他们的expectedContentLength并有您的最终下载大小。

 - (void)sizeTaskForURL:(NSURL *)url { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; [request setHTTPMethod:@"HEAD"]; NSURLSessionDataTask *sizeTask = [[[self class] dataSession] dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { if (error == nil) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if ([httpResponse statusCode] == 200) { totalBytesExpectedToReceive += (double)[httpResponse expectedContentLength]; numberOfFileSizesReceived++; NSLog(@"%lu/%lu files found. file size: %f", (unsigned long)numberOfFileSizesReceived, (unsigned long)numberOfTasks, totalBytesExpectedToReceive); if (numberOfFileSizesReceived == numberOfTasks){ NSLog(@"%lu/%lu files found. total file size: %f", (unsigned long)numberOfFileSizesReceived, (unsigned long)numberOfTasks, totalBytesExpectedToReceive); } } else { NSLog(@"Bad status code (%ld) for size task at URL: %@", (long)[httpResponse statusCode], [[response URL] absoluteString]); } } else { NSLog(@"Size task finished with error: %@", error.localizedDescription); } }]; [sizeTask resume]; } 

之后下载文件

这是它的样子:

在左边你可以看到,当它发送HEAD requests它还没有启动UIProgressView 。 完成后下载文件。

应用

所以,如果你下载大文件,那么“浪费”那些做HEAD请求的秒数可能是有用的,从而向用户显示正确的进度而不是一些错误的进度。

在下载过程中,您(绝对)想要使用委托方法来获取更小的新数据细分(否则进度视图将“跳转”)。

是啊。 我记得在1994年,当我写OmniWeb的时候,回想起了这件事。 我们尝试了许多解决scheme,包括仅仅让进度条旋转而不是显示进度(不stream行),或者随着新的任务计算出他们将被添加到队列中的大小,有时会逆转进展)。

最后,大多数程序决定使用(包括iOS 5和Safari中的消息)是一种骗局:例如,在消息中,他们知道发送消息的平均时间约为1.5秒(仅为示例数字),所以他们animation的进度条在1.5秒左右完成,如果邮件还没有真正发送,只会延迟到1.4秒。

现代浏览器(如Safari)通过将任务分成多个部分并显示每个部分的进度条来改变此方法。 就像(只是示例),Safari可能会认为在DNS中查找URL通常需要0.2秒,所以他们会在0.2秒内为进度条的前十分之一(或其他)制作animation,但是当然他们会如果DNS查找分别需要更短或更长时间,请跳过(或等待1/10分)。

在你的情况下,我不知道你的任务是多么可预测,但应该有一个类似的作弊。 比如,大多数文件的平均大小是多less? 如果是这样,你应该能够弄清楚50多长时间。 或者,您只需将进度条划分为50个分段,并在每次文件完成时填写一个分段,然后根据您获得的当前字节数/秒或根据您获得的文件数/秒数进行animation处理远远或任何你喜欢的其他指标。

一个诀窍就是如果你必须开始或停止进度条,那么使用芝诺的悖论 – 不要停下来或跳到下一个标记,而应该减慢(并保持减速)或加速(并继续加速),直到你已经到了酒吧需要的地方了。

祝你好运!

你的是“进度条”问题的一个特例。 授予,例如这个reddit线程 。

这个问题一直存在下去 ,正如Wil似乎所说的那样,即使Megacorp,Inc.™能够“如此”,也是非常困难的。 例如,即使有完全准确的MB值,只是由于networking问题,您可以轻松挂起而没有“进度”。 你的应用程序仍在工作,但是这个看法可能是你的程序被挂起了。

而提供非常“准确”值的任务可能会使进程任务过于复杂化。 仔细地踩。

我有两个可能的解决scheme给你。 既没有得到你正在寻找什么,但都给了用户一个正在发生的事情的理解。

第一个解决scheme,你有多个进度条。 指示文件编号进度的一个大条(200个中的50个)。 然后你下面有多个进度条(它们的数量等于可能下载的并发量,在我的情况下这是4,在你可能是笨拙的)。 所以用户知道关于下载的细节细节,并且获得总体进展(不随下载字节移动,而是下载完成)。

第二个解决scheme是一个多文件进度条。 这个可能是骗人的,因为这些文件的大小是不同的,但是你可以创build一个进度条,将其分割成许多等同于文件下载计数的块。 然后,根据单个文件的下载,每个块的块从0%到100%。 所以你可以填写进度条的中间部分,而开始和结束是空的(文件没有下载)。

再一次,这些都不是解决如何从多个文件中下载总字节数的问题,但是它们是可替代的UI,因此用户在任何时候都能理解所发生的一切。 (我最喜欢选项1)