Подтвердить что ты не робот

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

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

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

// as per: /info/110573/how-to-batch-request-with-afnetworking-2/689421#689421
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 progress callback method downloadProgressBlock , defined as ^ (NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {} ​​does not include the specific operation as a callback block variable (as opposed to the успешные обратные вызовы and fail, которые содержат определенную операцию как переменную, что делает ее доступной). Это делает невозможным, насколько я могу судить, определить, какая операция специально выполняет обратный вызов.

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Блоки сделаны только для облегчения этих вещей;-)

ЗДЕСЬ вы можете найти пример проекта. Просто нажмите кнопку + и вставьте прямой 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. Ссылка сохраняется и просмотр прогресса обновляется до тех пор, пока не будет достигнут блок выполнения.

Ответ 3

Вы должны загружать некоторый 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 Controller). Показать список всех загружаемых операций в этом классе

Для отображения прогресса. Наблюдайте за уведомлением и выбирайте объект модели из уведомления и получите идентификатор файла из уведомления и найдите этот идентификатор в представлении таблицы и обновите эту конкретную ячейку с прогрессом.

Ответ 4

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

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

Ответ 5

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

Вот простой способ его решить. Допустим, вы загружаете максимум 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));

[self array] будет выглядеть так:

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
)