如何使用AFNetworking跟踪多个同时下载的进度?

时间:2013-12-04 15:59:13

标签: ios block batch-processing mbprogresshud afnetworking-2

我正在使用AFNetworking下载我的应用用于同步解决方案的文件。在某些时候,应用程序会将一系列文件作为批处理单元下载。 Following this example,我这样运行批处理:

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,即可轻松更新进度表。但是,当您同时进行多次下载时,很难总体跟踪。

我考虑过为每个HTTP操作设置一个NSMutableDictionary密钥,使用这种通用格式:

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

随着每个操作的下载进展,我可以更新中心totalBytesRead中针对该特定操作的NSMutableDictionary - 然后将所有totalBytesReadtotalBytesExpected' to come up with the total for the whole batched operation. However, AFNetworking's 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 failure`回调,它确实包含特定操作作为变量,使其可访问)。据我所知,这使得无法确定哪个操作专门进行回调。

有关如何使用AFNetworking跟踪多极同步下载进度的任何建议?

5 个答案:

答案 0 :(得分: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变量的值。您可以认为它就像您在块中引用的每个变量一样,如果您的块立即执行了。

答案 1 :(得分:2)

只是为了使这些事情变得简单;-)

HERE您可以找到一个示例项目。只需按下+按钮并插入要下载的文件的直接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。保存引用并更新进度视图,直到进度块存在。

答案 2 :(得分:2)

您必须下载一些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控制器)。 显示此课程中的所有下载操作列表

显示进度观察通知并从通知中获取模型对象并从通知中获取文件ID并在表视图中查找该ID并使用该进度更新该特定单元格。

答案 3 :(得分:0)

启动操作时,您可以在NSDictionary中将每个操作,downloadID和totalbytesRead和totalBytesExpected的值一起保护,并将所有dicts保存到downloadProgressArray。

然后,当调用回调方法时,循环遍历数组并将调用操作与每个字典中的操作进行比较。通过这种方式,您应该能够识别操作。

答案 4 :(得分:0)

我做了一些非常相似的事情(上传了一堆文件而不是下载)

这是解决问题的简单方法。 假设您一次下载最多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
)