Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Average progress of all the NSURLSessionTasks in a NSURLSession

An NSURLSession will allow you to add to it a large number of NSURLSessionTask to download in the background.

If you want to check the progress of a single NSURLSessionTask, it’s as easy as

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

But what is the best way to check the average progress of all the NSURLSessionTasks in a NSURLSession?

I thought I’d try averaging the progress of all tasks:

[[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(@" ");

}];

But the logic here is wrong: Suppose you have 50 tasks expecting to download 1MB and 3 tasks expecting to download 100MB. If the 50 small tasks complete before the 3 large tasks, averageProgress will be much higher than the actual average progress.

So you have to calculate average progress according to the TOTAL countOfBytesReceived divided by the TOTAL countOfBytesExpectedToReceive. But the problem is that a NSURLSessionTask figures out those values only once it starts, and it might not start until another task finishes.

So how do you check the average progress of all the NSURLSessionTasks in a NSURLSession?

like image 637
Eric Avatar asked Dec 20 '13 11:12

Eric


1 Answers

Ah yes. I remember dealing with this back in 1994, when I wrote OmniWeb. We tried a number of solutions, including just having the progress bar spin instead of show progress (not popular), or having it grow as new tasks figured out how big they would be / got added to the queue (made users upset because they saw reverse progress sometimes).

In the end what most programs have decided to use (including Messages in iOS 5, and Safari) is a kind of cheat: for instance, in Messages they knew the average time to send a message was about 1.5 seconds (example numbers only), so they animated the progress bar to finish at about 1.5 seconds, and would just delay at 1.4 seconds if the message hadn’t actually gotten sent yet.

Modern browsers (like Safari) vary this approach by dividing the task into sections, and showing a progress bar for each section. Like (example only), Safari might figure that looking up a URL in DNS will usually take 0.2 seconds, so they’ll animate the first 1/10th (or whatever) of the progress bar over 0.2 seconds, but of course they’ll skip ahead (or wait at the 1/10th mark) if the DNS lookup takes shorter or longer respectively.

In your case I don’t know how predictable your task is, but there should be a similar cheat. Like, is there an average size for most files? If so, you should be able to figure out about how long 50 will take. Or you just divide your progress bar into 50 segments and fill in a segment each time a file completes, and animate based on the current number of bytes / second you’re getting or based on the number of files / second you’ve gotten so far or any other metric you like.

One trick is to use Zeno’s paradox if you have to start or stop the progress bar—don’t just halt or jump to the next mark, instead just slow down (and keep slowing down) or speed up (and keep speeding up) until you’ve gotten to where the bar needs to be.

Good luck!

like image 58
Wil Shipley Avatar answered Oct 18 '22 02:10

Wil Shipley