Скачивайте файлы по одному с помощью NSURLSessionDownloadTask

В моем приложении я получаю JSON из веб-службы. Этот JSON содержит URL-адреса нескольких файлов, которые я хотел бы загрузить.

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

Заранее большое спасибо !


person Vinestro    schedule 10.10.2014    source источник
comment
Я так понимаю, мое решение не то, что вы ищете? Пожалуйста, прокомментируйте и уточните, как это неправильно?   -  person Daniel Galasko    schedule 12.10.2014


Ответы (1)


NSURLSessionDownloadTask, как вы хорошо знаете, не очень хорошо работает с NSOperationQueues, в отличие от их аналога NSURLConnection (где он может быть инкапсулирован внутри NSOperation).

Одним из вариантов было бы добавить все ваши URL-адреса в массив, а затем внутри обработчика завершения задачи просто поставить в очередь следующий элемент.

Таким образом, вы можете создавать свои задачи в цикле, вызывать progressBlock внутри каждого обработчика завершения задач, сохранять задачи в массиве и ставить следующую задачу в очередь внутри каждого обработчика завершения задач:

- (void)addRequestsWithURLs:(NSArray *)urls
              progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations, NSURLSessionDownloadTask *task,NSURL *location, NSURLResponse *response, NSError *error))progressBlock {
    __block NSUInteger numberOfFinishedOperations = 0;
    NSUInteger totalNumberOfOperations = [urls count];
    for (NSString *url in urls) {
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
        __block NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request
                                                                completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
                                                                   //downloadFileSomewhere

                                                                    ++numberOfFinishedOperations;
                                                                        if (progressBlock) {
                                                                            progressBlock(numberOfFinishedOperations, totalNumberOfOperations,task,destination != nil ? [NSURL fileURLWithPath:destination] : nil,response,error);

                                                                        }
                                                                    //queueNext
                                                                    [self processCompletedTask:task];
                                                                }];
        //stores an array of NSURLSessionTasks
        [self.tasksWaitingToBeQueued addObject:task];
    }

}

- (void)processCompletedTask:(NSURLSessionTask *)completedTask {
    //clean up and queue next one
    [self.tasksWaitingToBeQueued removeObject:completedTask];        
    nextTask = [self.tasksWaitingToBeQueued firstObject];
    if (nextTask) {
        [nextTask resume];
    }
}

ПРИМЕЧАНИЕ

В этом примере я показываю прогресс как количество выполненных задач, а не количество байтов, это рекомендуемый подход (он также проще). Чтобы указать прогресс с использованием байтов, вам необходимо заранее знать общее количество байтов для загрузки (поскольку вы хотите показать индикатор выполнения), а также реализовать делегат NSURLSession и отслеживать ход выполнения каждой задачи, захватить загруженные байты и обновить свой блок. . Если ваш сервер не сообщает вам общее количество байтов, вам, вероятно, потребуется выполнить запрос HEAD для каждого ресурса и агрегировать размеры. Лично это решение слишком сложно для того, что можно просто решить, указав прогресс в виде количества загруженных файлов.

Для достижения этого может выглядеть примерно так:

- (void)URLSession:(NSURLSession *)session 
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
 totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    self.totalBytesWritten += totalBytesWritten;
    NSUInteger totalProgressSoFar = self.totalBytesWritten;
    NSUInteger totalExpectedBytes = self.totalExpectedBytes;
    //you would need to capture some progress block locally - beware of retain cycles
    self.progressBlock(totalProgressSoFar/totalExpectedBytes)
}

когда вы закончите, вы должны установить progressBlock на nil, чтобы предотвратить любые циклы сохранения.

person Daniel Galasko    schedule 10.10.2014
comment
Большое спасибо за ответ. Я использовал то, что вы предложили, и, похоже, это работает. Спасибо еще раз ! - person Vinestro; 13.10.2014
comment
@Vinestro круто! рад это слышать :) - person Daniel Galasko; 14.10.2014