以下の私の最初の回答では、非同期画像検索に関連するいくつかの重要な問題を解決しようとしましたが、それでも根本的に制限されています。適切な実装では、すばやくスクロールした場合に、表示されているセルが画面外にスクロールされたセルよりも優先されるようにすることもできます。また、以前のリクエストのキャンセルもサポートします (必要に応じてキャンセルします)。
この種の機能を以下のコードに追加することもできますが、以下で説明するNSOperationQueue
とのNSCache
テクノロジを利用するだけでなく、上記の問題にも対処する、確立された実績のあるソリューションを採用することをお勧めします。最も簡単な解決策は、UIImageView
非同期の画像検索をサポートする確立されたカテゴリの 1 つを採用することです。AFNetworkingライブラリとSDWebImageライブラリの両方UIImageView
に、これらすべての問題を適切に処理するカテゴリがあります。
NSOperationQueue
またはGCDを使用して遅延読み込みを実行できます (さまざまな非同期操作テクノロジについては、同時実行プログラミング ガイドを参照してください)。前者には、許可される同時操作の数を正確に指定できるという利点があります。これは、Web から画像をロードする際に非常に重要です。これは、多くの Web サーバーが特定のクライアントから受け入れる同時要求の数を制限しているためです。
基本的な考え方は次のとおりです。
- 画像データのリクエストを別のバックグラウンド キューに送信します。
- 画像のダウンロードが完了したら、バックグラウンドで UI の更新を行うべきではないため、UI の更新をメイン キューにディスパッチします。
- ディスパッチされた最終的な UI 更新コードをメイン キューで実行するときは、
UITableViewCell
がまだ表示されていること、問題のセルが画面外にスクロールしたためにキューから取り出されて再利用されていないことを確認してください。そうしないと、間違った画像が一時的に表示される場合があります。
コードを次のコードのようなものに置き換えます。
まず、NSOperationQueue
画像のダウンロードNSCache
と保存に使用する のプロパティを定義します。
@property (nonatomic, strong) NSOperationQueue *imageDownloadingQueue;
@property (nonatomic, strong) NSCache *imageCache;
次に、このキューとキャッシュを初期化しますviewDidLoad
。
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageDownloadingQueue = [[NSOperationQueue alloc] init];
self.imageDownloadingQueue.maxConcurrentOperationCount = 4; // many servers limit how many concurrent requests they'll accept from a device, so make sure to set this accordingly
self.imageCache = [[NSCache alloc] init];
// the rest of your viewDidLoad
}
第三に、次のcellForRowAtIndexPath
ようになります。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
btnBack.hidden = FALSE;
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.font = [UIFont fontWithName:@"Noteworthy" size:17.0];
cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
cell.textLabel.textColor = [UIColor blackColor];
cell.textLabel.highlightedTextColor = [UIColor blackColor];
}
cell.textLabel.text = [NSString stringWithFormat:@" %@", [test.arrTitle objectAtIndex:indexPath.row]];
// code change starts here ... initialize image and then do image loading in background
NSString *imageUrlString = [NSString stringWithFormat:@"http://%@", [test.arrImages objectAtIndex:indexPath.row]];
UIImage *cachedImage = [self.imageCache objectForKey:imageUrlString];
if (cachedImage) {
cell.imageView.image = cachedImage;
} else {
// you'll want to initialize the image with some blank image as a placeholder
cell.imageView.image = [UIImage imageNamed:@"blankthumbnail.png"];
// now download in the image in the background
[self.imageDownloadingQueue addOperationWithBlock:^{
NSURL *imageUrl = [NSURL URLWithString:imageUrlString];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = nil;
if (imageData)
image = [UIImage imageWithData:imageData];
if (image) {
// add the image to your cache
[self.imageCache setObject:image forKey:imageUrlString];
// finally, update the user interface in the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Make sure the cell is still visible
// Note, by using the same `indexPath`, this makes a fundamental
// assumption that you did not insert any rows in the intervening
// time. If this is not a valid assumption, make sure you go back
// to your model to identify the correct `indexPath`/`updateCell`
UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
updateCell.imageView.image = image;
}];
}
}];
}
return cell;
}
最後に、メモリ不足の状況でキャッシュを消去するコードを書きたくなるかもしれませんが、これは自動的に行われるため、ここでは追加の処理は必要ありません。シミュレーターでメモリ不足の状況を手動でシミュレートする場合、 がNSCache
応答しないため、オブジェクトを追い出すことはありませんUIApplicationDidReceiveMemoryWarningNotification
が、実際の操作中にメモリが少なくなると、キャッシュが消去されます。実際には、NSCache
メモリ不足の状況自体に適切に応答しなくなったため、この通知のオブザーバーを追加し、メモリ不足の状況ではキャッシュを空にする必要があります。
他の最適化を提案するかもしれません (たとえば、イメージを永続的なストレージにキャッシュして、将来の操作を合理化することもできます。実際には、このすべてのロジックを自分のAsyncImage
クラスに入れています) が、まず、これで基本的なパフォーマンスの問題が解決するかどうかを確認してください。