NSURLSession
允许您在后台添加大量NSURLSessionTask
进行下载。
如果您想检查单个NSURLSessionTask
的进度,就像
double taskProgress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;
但检查 NSURLSessionTasks
中所有NSURLSession
的平均进度的最佳方法是什么?
我以为我会尝试平均所有任务的进度:
[[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的任务。如果在3个大任务之前完成50个小任务,averageProgress
将远高于实际平均进度。
因此,您必须根据 TOTAL countOfBytesReceived
除以 TOTAL countOfBytesExpectedToReceive
计算平均进度。但问题是NSURLSessionTask
只有在启动时才会计算出这些值,并且在另一个任务完成之前它可能无法启动。
那么如何检查 NSURLSessionTasks
中所有NSURLSession
的平均进度?
答案 0 :(得分:15)
啊,是的。我记得1994年我写过OmniWeb的时候处理过这个问题。我们尝试了许多解决方案,包括只是让进度条旋转而不是显示进度(不受欢迎),或者随着新任务计算出它们将被添加到队列中的大小而增长(让用户感到不安因为他们看到了有时反转进展。)
最终大多数程序决定使用(包括iOS 5和Safari中的消息)是一种欺骗:例如,在消息中他们知道发送消息的平均时间大约是1.5秒(示例数字)只有),所以他们动画进度条大约1.5秒完成,如果消息实际上还没有发送,它只会延迟1.4秒。
现代浏览器(如Safari)通过将任务划分为多个部分并显示每个部分的进度条来改变这种方法。就像(仅限示例)一样,Safari可能会认为在DNS中查找URL通常需要0.2秒,因此他们会在0.2秒内为进度条的前1/10(或其他)设置动画,但当然他们会如果DNS查找分别更短或更长,则向前跳过(或等待1/10标记)。
在你的情况下,我不知道你的任务是多么可预测,但应该有类似的欺骗。比如,大多数文件的平均大小是多少?如果是这样,你应该能够弄清楚50需要多长时间。或者,您只需将进度条划分为50个段,并在每次文件完成时填写一个段,并根据您获得的当前字节数/秒或根据您获得的文件数/秒来设置动画。您喜欢的任何其他指标。
如果你必须开始或停止进度条,一个诀窍就是使用Zeno的悖论 - 不要只是停下来或跳到下一个标记,而只是减速(并继续减速)或加速(并保持超速)直到你到达酒吧需要的地方为止。
祝你好运!答案 1 :(得分:13)
在开始下载文件之前,您可以发送微小的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请求的秒数可能会很有用,从而向用户显示正确的进度而不是一些错误的进度。
在下载过程中,您(当然)希望使用委托方法来获取新数据的较小细分(否则进度视图将“跳转”)。
答案 2 :(得分:3)
你的是“进度条”问题的具体情况。例如,提供这reddit thread。
它一直在永远,正如Wil似乎在说,即使Megacorp,Inc。™得到“就是这样”也难以实现。例如。即使有完全准确的MB值,由于网络问题,您可以轻松地挂起而没有“进展”。您的应用仍在运行,但感知可能是您的程序已挂起。
提供极其“准确”的值可能会使进度任务过于复杂。小心翼翼。
答案 3 :(得分:2)
我有两种可能的解决方案。既没有得到你想要的东西,但都让用户了解正在发生的事情。
首先解决方案是你有多个进度条。一个指示文件编号进度的大条(完成200个中的50个)。然后你下面有多个进度条(它们的数量等于可能的并发下载量,在我的情况下这是4,在你的可能是不实用的)。因此,用户知道有关下载的细粒度细节,并获得总体进度(不随下载字节移动,但下载完成)。
第二个解决方案是一个多文件进度条。这个可能是骗人的,因为文件大小不同,但您可以创建一个进度条,该进度条被分成多个块,等于您的文件下载次数。然后根据单个文件的下载,条形图的每个块从0%到100%。因此,您可以填充进度条的中间部分,而开头和结尾都是空的(文件未下载)。
同样,这些都不是解决如何从多个文件中获取总字节数的问题的解决方案,但它们是备用UI,因此用户可以理解在任何给定时间发生的所有事情。 (我最喜欢选项1)