要件:
コレクションビューを表示しているView Controllerクラスがあります。セルごとに、画像がローカルに既に存在するかどうかを確認しています。存在しない場合は、サーバーから画像をダウンロードして同じものを表示しようとしています。
実装:
以下は同じコードです。
- (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 で開始されたものとは異なる識別子を取得するのはなぜですか?
これが予想される動作である場合、私の要件を実装する最良の方法は何ですか?