NSURLSessionDownloadTask 并发下载,app 后台也支持,没有按预期工作

Posted

技术标签:

【中文标题】NSURLSessionDownloadTask 并发下载,app 后台也支持,没有按预期工作【英文标题】:NSURLSessionDownloadTask concurrent downloads, also supported in app background, not working as expected 【发布时间】:2014-01-26 07:24:31 【问题描述】:

要求:

我有一个视图控制器类,我在其中显示一个集合视图。在每个单元格中,我正在检查图像是否已经存在于本地,如果它不存在,那么我正在尝试从服务器下载图像并显示相同。

实施:

下面是相同的代码:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    // retrieving associated product
    Products *aProduct = [self.fetchedResultsController objectAtIndexPath:indexPath];

    UICollectionViewCell* newCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:kProductIconIdentifier forIndexPath:indexPath];

    // setup of image view and cell label
    UIImageView *cellImageView = (UIImageView *)[collectionView viewWithTag:kImageViewTag];
    cellImageView.contentMode = UIViewContentModeScaleAspectFill;

    UILabel *cellLabel = (UILabel *)[newCell viewWithTag:klabelTag];

    // assigning value to cell label and image view
    NSString *productImageLocalPath = aProduct.imageLocalPath;
    if ([[NSFileManager defaultManager] fileExistsAtPath:productImageLocalPath]) 
        // file exists at local path :-)
        // means less fun :-(
        cellImageView.image = [UIImage imageWithContentsOfFile:productImageLocalPath];
    
    else
    
        UIActivityIndicatorView *downloadActivityIndicator = (UIActivityIndicatorView *)[newCell viewWithTag:kActivityIndicator];
        downloadActivityIndicator.hidden = NO;
        [downloadActivityIndicator startAnimating];

        // file does not exist at local path :-(
        // means more fun :-)
        [self.sessionController setupAndStartDownloadTaskForProduct:aProduct withCompletionHandler:^(NSString * tempLocalPath)
            // download was successful

            NSData *imageData = [[NSData alloc] initWithContentsOfFile:tempLocalPath];
            [imageData writeToFile:productImageLocalPath atomically:YES];

            cellImageView.image = [UIImage imageWithData:imageData];
            [downloadActivityIndicator stopAnimating];

         andFailureHandler:^
            cellImageView.image = nil;
            [downloadActivityIndicator stopAnimating];
        ];
    

    // setting values
    cellLabel.text = aProduct.imageName;

    return newCell;

在会话控制器类中,我有以下方法来启动一个新的下载任务:

- (void)setupAndStartDownloadTaskForProduct:(Products *)aProduct withCompletionHandler:(DownloadedCompletionHandler)completionHandler andFailureHandler:(DownloadedFailureHandler)failureHandler

    NSString *completeImagePath = [kBasePath stringByAppendingPathComponent:aProduct.imageRelativePath];

    NSURL *downloadURL = [NSURL URLWithString:completeImagePath];

    if (!self.session) 
        [self setUpSession];
    

    NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:downloadURL];

    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:downloadRequest];

    NSDictionary *downloadInfoDict = @kSuccessHandlerKey: [completionHandler copy], kFailureHandlerKey: [failureHandler copy];

    self.downloadTasks[@(downloadTask.taskIdentifier)] = downloadInfoDict;

    // resuming the download task
    [downloadTask resume];


在上述方法中,我将 successHandler 和 failureHandler 块存储在字典中,使用其任务标识符将其映射到下载任务。

下面是 didFinishDownloadingToURL 方法的实现:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

    dispatch_async(dispatch_get_main_queue(), ^
        // invoking success block
        DownloadedCompletionHandler successCompletionHandler = self.downloadTasks[@(downloadTask.taskIdentifier)][kSuccessHandlerKey];
        successCompletionHandler([location path]);

        // removing download task key-value pair from dictionary
        [self.downloadTasks removeObjectForKey:@(downloadTask.taskIdentifier)];
    );

我的问题是 - 有时在上述方法中,downloadTask 返回的标识符与在 setupAndStartDownloadTaskForProduct 中启动的标识符不同,因此 successCompletionHandler 被获取为 nil,当我尝试调用处理程序块时应用程序崩溃.

现在我的问题是:

    为什么在 didFinishDownloadingToURL 中我得到的标识符与在 setupAndStartDownloadTaskForProduct 中启动的标识符不同?

    如果这是预期行为,那么实现我的要求的最佳方式是什么?

【问题讨论】:

你能把你的代码发给self.session吗? 【参考方案1】:

您使用的 taskIdentifier 显然是 NSUInteger。您正在做的是使用 NSNumber 作为具有文字快捷方式的键:

NSNumber * key = @(downloadTask.taskIdentifier);

问题在于,使用 NSString 以外的任何内容作为字典中的键是一个坏主意。您所做的是创建一个具有相同值但不保证 -isEqual: 评估为真的 NSNumber 对象。

不使用 NSNumber 键,而是将键转换为字符串:

NSString * key = [NSString stringWithFormat:@"%d", downloadTask.taskIdentifier];

然后字典散列将起作用。

【讨论】:

【参考方案2】:

您是否检查过 this post 将图像延迟加载到 UITableView 中?

也许你可以为 UICollectionView 自定义它。

希望这会有所帮助!

【讨论】:

该链接与发布者使用的内容相比严重退步。

以上是关于NSURLSessionDownloadTask 并发下载,app 后台也支持,没有按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSessionDownloadTask 进度回调不顺畅?

在 NSMutableDictionary 中设置 NSURLSessionDownloadTask

NSUrlSessionDownloadTask - 进入后台时出现didCompleteWithError

iOS开发之网络编程--2NSURLSessionDownloadTask文件下载

NSURLSessionDownloadTask 在挂起时继续下载

在下载字节时从 NSURLSessionDownloadTask 访问字节