Как отслеживать ход нескольких одновременных загрузок с помощью AFNetworking?

Я использую AFNetworking для загрузки файлов, которые мое приложение использует для решения синхронизации. В определенное время приложение загружает серию файлов в виде пакетной единицы. Следуя этому примеру, я запускаю пакет следующим образом:

NSURL *baseURL = <NSURL with the base of my server>;
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];

// as per: https://stackoverflow.com/a/19883392/353137
dispatch_group_t group = dispatch_group_create();

for (NSDictionary *changeSet in changeSets) {
    dispatch_group_enter(group);

    AFHTTPRequestOperation *operation =
    [manager
     POST:@"download"
     parameters: <my download parameters>
     success:^(AFHTTPRequestOperation *operation, id responseObject) {
         // handle download success...
         // ...
         dispatch_group_leave(group);

     }
     failure:^(AFHTTPRequestOperation *operation, NSError *error) {
         // handle failure...
         // ...
         dispatch_group_leave(group);
     }];
    [operation start];
}
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // run code when all files are downloaded
});

Это хорошо работает для пакетных загрузок. Однако я хочу показать пользователю MBProgressHUD, который показывает им, как идут загрузки.

AFNetworking предоставляет метод обратного вызова

[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
}];

... что позволяет довольно легко обновлять индикатор прогресса, просто установив прогресс на totalBytesRead / totalBytesExpectedToRead. Но когда у вас одновременно происходит несколько загрузок, их трудно отслеживать в целом.

Я подумал о том, чтобы иметь NSMutableDictionary с ключом для каждой операции HTTP в этом общем формате:

NSMutableArray *downloadProgress = [NSMutableArray arrayWithArray:@{
   @"DownloadID1" : @{ @"totalBytesRead" : @0, @"totalBytesExpected" : @100000},
   @"DownloadID2" : @{ @"totalBytesRead" : @0, @"totalBytesExpected" : @200000}
}];

По мере загрузки каждой операции я могу обновить totalBytesRead для этой конкретной операции в центральном NSMutableDictionary -- а затем суммировать все обратные вызовы totalBytesRead и totalBytesExpected' to come up with the total for the whole batched operation. However, AFNetworking's progress callback methoddownloadProgressBlock, defined as^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead){}does not include the specific operation as a callback block variable (as opposed to thesuccessandfailure`, которые содержат конкретную операцию как переменную, что делает ее доступной). Что делает невозможным, насколько я могу судить, определить, какая конкретно операция выполняет обратный вызов.

Любые предложения о том, как отслеживать ход многополюсных одновременных загрузок с помощью AFNetworking?


person Jason    schedule 04.12.2013    source источник
comment
Может быть плохой идеей: создайте AFHTTPRequestOperationManager для любого NSDictionary.   -  person Dimentar    schedule 06.12.2013
comment
Вы видели этот ответ? stackoverflow.com/questions/15161758/   -  person maross    schedule 06.12.2013
comment
@maross Нет, я не видел этого ответа. Это похоже на то, о чем я думаю. Однако должен ли блок знать, к какому Download объекту он относится? Учитывая, что когда вы просматриваете список подключений для загрузки, он продолжает воссоздавать объект Download.   -  person Jason    schedule 06.12.2013
comment
Я только что сделал что-то очень похожее, просто загрузив пакет файлов. Код в моем ответе отлично работает без каких-либо специальных ссылок на каждую операцию.   -  person Segev    schedule 11.12.2013


Ответы (5)


Если ваш блок встроен, вы можете получить доступ к operation напрямую, но компилятор может предупредить вас о циклической ссылке. Вы можете обойти это, объявив слабую ссылку и используя ее внутри блока:

__weak AFHTTPRequestOperation weakOp = operation;
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
    NSURL* url = weakOp.request.URL; // sender operation's URL
}];

На самом деле вы можете получить доступ к чему угодно внутри блока, но для этого вам нужно понимать блок. Как правило, любая переменная, указанная в блоке, копируется во время создания блока, т. е. во время выполнения строки. Это означает, что мой weakOp в блоке будет ссылаться на значение переменной weakOp при выполнении строки setDownloadProgressBlock. Вы можете представить себе, какой будет каждая переменная, на которую вы ссылаетесь в блоке, если ваш блок будет выполнен немедленно.

person tia    schedule 08.12.2013

Блоки сделаны только для того, чтобы упростить эти вещи ;-)

ЗДЕСЬ вы можете найти пример проекта. Просто нажмите кнопку + и введите прямой URL-адрес файла для загрузки. Отсутствует проверка ошибок и перенаправление URL-адресов, поэтому вставляйте только прямые URL-адреса.
Соответствующую часть см. в этих методах DownloadViewController:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

Здесь объяснение:

Когда вы передаете переменную в блок, блок делает копию переменных, переданных извне. Поскольку наша переменная — это просто указатель на объект (адрес памяти), указатель копируется внутрь блока, а поскольку хранилище по умолчанию — __strong, ссылка сохраняется до тех пор, пока блок не будет уничтожен.

Это означает, что вы можете передать блоку прямую ссылку на ваш прогресс (я использую UIProgressView, так как я никогда не использовал MBProgressHUD):

UIProgressView *progressView = // create a reference to the view for this specific operation

[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {

    progressView.progress = // do calculation to update the view. If the callback is on a background thread, remember to add a dispatch on the main queue
}];

При этом каждая операция будет иметь ссылку на собственный progressView. Ссылка сохраняется, а представление прогресса обновляется до тех пор, пока не существует блок прогресса.

person LombaX    schedule 09.12.2013

Вы должны загрузить какой-то zip-файл, видеофайл и т. д. Создайте модель этого файла для загрузки, содержащую такие поля, как (id, url, image, type и т. д.)

Создайте NSOperationQueue и установите maxConcurrentOperationCount в соответствии с вашим требованием сделать общедоступным метод. (в одноэлементном классе)

- (void)downloadfileModel:(ModelClass *)model {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.zip",model.iD]];


    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:model.url]];
    operation = [[AFDownloadRequestOperation alloc] initWithRequest:request targetPath:path shouldResume:YES];

    operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];

    [operation setUserInfo:[NSDictionary dictionaryWithObject:model forKey:@"model"]];
    ////// Saving model into operation dictionary

    [operation setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {

  ////// Sending Notification 
  ////// Try to send notification only on significant download          
                        totalBytesRead = ((totalBytesRead *100)/totalBytesExpectedToReadForFile);
                                [[NSNotificationCenter defaultCenter] postNotificationName:DOWNLOAD_PROGRESS object:model userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"%lld",totalBytesRead] forKey:@"progress"]];
/// Sending progress notification with model object of operation

                    }

    }];

[[self downloadQueue] addOperation:operation];  // adding operation to queue

}

Вызовите этот метод несколько раз с разными моделями для нескольких загрузок.

Обратите внимание на это уведомление на контроллере, где вы показываете ход загрузки. (возможно, контроллер tableView). Показать список всех операций загрузки в этом классе

Чтобы показать прогресс, просмотрите уведомление и извлеките объект модели из уведомления, получите идентификатор файла из уведомления, найдите этот идентификатор в табличном представлении и обновите эту конкретную ячейку с прогрессом.

person Dinesh Kaushik    schedule 13.12.2013
comment
Я получаю ошибку перемещения. У меня есть вопрос здесь - person Siriss; 11.12.2014
comment
@Siriss Какой тип ошибки вы получаете, предоставьте более подробную информацию об ошибке ?? - person Dinesh Kaushik; 11.12.2014
comment
У меня есть ссылка в моем комментарии выше. - person Siriss; 11.12.2014

при запуске операций вы можете сохранить каждую операцию, идентификатор загрузки и значения для totalbytesRead и totalBytesExpected вместе в NSDictionary и все словари в вашем downloadProgressArray.

Затем, когда вызываются методы обратного вызова, прокрутите свой массив и сравните вызывающую операцию с операцией в каждом словаре. Таким образом, вы сможете идентифицировать операцию.

person thorb65    schedule 07.12.2013

Я только что сделал что-то очень похожее (загрузил кучу файлов вместо загрузки)

Вот простой способ решить эту проблему. Допустим, вы загружаете максимум 10 файлов за один раз.

__block int random=-1;
    [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {

        if (random == -1) // This chunk of code just makes sure your random number between 0 to 10 is not repetitive
        {
            random = arc4random() % 10;
            if(![[[self myArray]objectAtIndex:random]isEqualToString:@"0"])
            {
                while (![[[self myArray]objectAtIndex:random]isEqualToString:@"0"])
                {
                    random = arc4random() % 10;
                }
            }
            [DataManager sharedDataManager].total += totalBytesExpectedToWrite;
        }

        [[self myArray] replaceObjectAtIndex:random withObject:[NSString stringWithFormat:@"%lu",(unsigned long)totalBytesWritten]];

Затем вы вычисляете это так:

NSNumber * sum = [[self myArray] valueForKeyPath:@"@sum.self"];
float percentDone;
percentDone = ((float)((int)[sum floatValue]) / (float)((int)[DataManager sharedDataManager].total));

[сам массив] будет выглядеть так:

array: (
    0,
    444840, // <-- will keep increasing until download is finished
    0,
    0,
    0,
    442144, // <-- will keep increasing until download is finished
    0,
    0,
    0,
    451580  // <-- will keep increasing until download is finished
)
person Segev    schedule 10.12.2013