Как отслеживать прогресс нескольких одновременных загрузок с помощью 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?
Ответы
Ответ 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
)