Средний прогресс всех задач NSURLSession в NSURLSession

An NSURLSession позволит вам добавить к нему большое количество NSURLSessionTask для загрузки в фоновом режиме.

Если вы хотите проверить ход одного NSURLSessionTask, его так же просто, как

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

Но каков наилучший способ проверить средний прогресс всех NSURLSessionTasks в NSURLSession?

Я думал, что Id пытается усреднить ход всех задач:

[[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 задач, ожидающих загрузки 1 МБ и 3 задач, ожидающих загрузки 100 МБ. Если 50 небольших задач выполняются до 3 больших задач, averageProgress будет намного выше, чем фактический средний прогресс.

Итак, вы должны рассчитать средний прогресс в соответствии с TOTAL countOfBytesReceived, деленным на TOTAL countOfBytesExpectedToReceive. Но проблема в том, что NSURLSessionTask определяет эти значения только после его запуска, и может не запускаться до завершения другой задачи.

Итак, как вы можете проверить средний прогресс всех NSURLSessionTasks в NSURLSession?

Ответы

Ответ 1

Прежде чем приступить к загрузке файлов, вы можете отправить маленький маленький 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. По завершении он загружает файлы.

App

Итак, если вы загружаете большие файлы, может быть полезно "отбросить" эти секунды, делая HEAD-запросы, и впредь покажет пользователю правильный прогресс, а не какой-то неправильный прогресс.

Во время загрузки вы (определенно) хотите использовать методы делегата для получения меньших подразделений новых данных (иначе просмотр прогресса будет "прыгать" ).

Ответ 2

Ах да. Я помню, как занимался этим еще в 1994 году, когда писал OmniWeb. Мы попробовали ряд решений, в том числе просто запустив индикатор прогресса, а не показывали прогресс (не популярный), или увеличили его, поскольку новые задачи выяснили, насколько они будут/добавлены в очередь (заставили пользователей расстраиваться, потому что видели иногда наоборот).

В конце концов, большинство программ, которые решили использовать (в том числе Сообщения в iOS 5 и Safari), - это своего рода чит: например, в сообщениях, которые они знали, среднее время отправки сообщения составляло около 1,5 секунд (номера примеров только), поэтому они оживили индикатор выполнения, чтобы закончить примерно 1,5 секунды, и будут просто задерживаться на 1,4 секунды, если сообщение еще не было отправлено.

Современные браузеры (например, Safari) меняют этот подход, деля задачу на разделы и отображая индикатор выполнения для каждого раздела. Как и в случае с примером (только для примера), Safari может показаться, что поиск URL-адреса в DNS обычно занимает 0,2 секунды, поэтому они оживляют первый 1/10-й (или любой другой) индикатор выполнения за 0.2 секунды, но, конечно, они проскакивают вперед (или подождите 1/10-й знак), если поиск DNS будет короче или длиннее, соответственно.

В вашем случае я не знаю, насколько предсказуема ваша задача, но должен быть похожий обман. Например, есть ли средний размер для большинства файлов? Если это так, вы должны быть в состоянии выяснить, сколько времени займет 50. Или вы просто разделяете свой индикатор выполнения на 50 сегментов и заполняете сегмент каждый раз, когда файл завершается, и оживляете на основе текущего количества байт/секунду, которое вы получаете, или на основе количества файлов/секунд, которые вы получили до сих пор, или любого другого метрику, которая вам нравится.

Один трюк заключается в том, чтобы использовать парадокс Зеноса, если вам нужно запустить или остановить индикатор прогресса - просто не останавливайтесь или не переходите к следующей отметке, а просто замедляете (и продолжаете замедлять) или ускоряетесь (и продолжаете ускоряться) пока вы не попали туда, где должен быть бар.

Удачи!

Ответ 3

Ваш конкретный случай проблемы "прогресс-бар". Конферировать, например. это reddit thread.

Это было навсегда, и, как говорилось, Wil, это может быть затруднительно даже для Megacorp, Inc. ™, чтобы получить "именно так", Например. даже с полностью точными значениями MB, вы можете легко висеть без "прогресса", просто из-за сетевых проблем. Ваше приложение все еще работает, но может показаться, что ваша программа висит.

И стремление обеспечить чрезвычайно "точные" значения может нарушить сложную задачу. Протектор осторожно.

Ответ 4

У меня есть два возможных решения для вас. Вы также не получите то, что ищете, но оба дают пользователю понимание того, что происходит.

Первое решение у вас есть несколько индикаторов выполнения. Один большой бар, который показывает число файлов (завершено 50 из 200). Затем у вас есть несколько индикаторов выполнения (число из которых равно количеству одновременных загрузок, в моем случае это 4, в вашем случае это может быть громоздким). Таким образом, пользователь знает как мелкие мелочи о загрузках, так и общий общий прогресс (который не перемещается с байтами загрузки, а с завершением загрузки).

Второе решение - индикатор состояния нескольких файлов. Это может быть обманчивым, потому что файлы имеют разные размеры, но вы можете создать индикатор выполнения, который разрезан на несколько кусков, равный количеству загружаемых файлов. Затем каждый фрагмент панели переходит от 0% до 100% на основе загрузки одного файла. Таким образом, вы можете иметь средние разделы индикатора выполнения, заполненные в начале и в конце (файлы не загружены).

Опять же, ни одно из них не является решением вопроса о том, как получить общее количество байтов для загрузки из нескольких файлов, но они являются альтернативным пользовательским интерфейсом, поэтому пользователь понимает, что происходит в любой момент времени. (Мне нравится вариант 1 лучше всего)