行をプリフェッチする場合は、メソッドに応答してUIScrollViewDelegate
、テーブルのスクロールがいつ完了したかを判断し、行のプリフェッチをトリガーできます。次を使用してプリフェッチを実行できますSDWebImagePrefetcher
(私の最初の回答では、この便利なクラスを少し否定していましたが、現在は比較的うまく機能しているようです)。
- (void)viewDidLoad
{
[super viewDidLoad];
// the details don't really matter here, but the idea is to fetch data,
// call `reloadData`, and then prefetch the other images
NSURL *url = [NSURL URLWithString:kUrlWithJSONData];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(@"sendAsynchronousRequest error: %@", connectionError);
return;
}
self.objects = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[self.tableView reloadData];
[self prefetchImagesForTableView:self.tableView];
}];
}
// some of the basic `UITableViewDataDelegate` methods have been omitted because they're not really relevant
これは簡単ですcellForRowAtIndexPath
(完全に関連SDWebImagePrefetcher
しているわけではありませんが、を使用する場合は、をいじる必要がないことを示しているだけですcellForRowAtIndexPath
:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSAssert([cell isKindOfClass:[CustomCell class]], @"cell should be CustomCell");
[cell.customImageView setImageWithURL:[self urlForIndexPath:indexPath] placeholderImage:nil];
[cell.customLabel setText:[self textForIndexPath:indexPath]];
return cell;
}
これらのUIScrollViewDelegate
メソッドは、スクロールが終了したときにさらに行をプリフェッチします
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// if `decelerate` was true for `scrollViewDidEndDragging:willDecelerate:`
// this will be called when the deceleration is done
[self prefetchImagesForTableView:self.tableView];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// if `decelerate` is true, then we shouldn't start prefetching yet, because
// `cellForRowAtIndexPath` will be hard at work returning cells for the currently visible
// cells.
if (!decelerate)
[self prefetchImagesForTableView:self.tableView];
}
明らかに、プリフェッチ ルーチンを実装する必要があります。これはNSIndexPath
、表示されているセルの両側にあるセルの値を取得し、それらの画像 URL を取得してから、そのデータをプリフェッチします。
/** Prefetch a certain number of images for rows prior to and subsequent to the currently visible cells
*
* @param tableView The tableview for which we're going to prefetch images.
*/
- (void)prefetchImagesForTableView:(UITableView *)tableView
{
NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
if ([indexPaths count] == 0) return;
NSIndexPath *minimumIndexPath = indexPaths[0];
NSIndexPath *maximumIndexPath = [indexPaths lastObject];
// they should be sorted already, but if not, update min and max accordingly
for (NSIndexPath *indexPath in indexPaths)
{
if (indexPath.section < minimumIndexPath.section || (indexPath.section == minimumIndexPath.section && indexPath.row < minimumIndexPath.row)) minimumIndexPath = indexPath;
if (indexPath.section > maximumIndexPath.section || (indexPath.section == maximumIndexPath.section && indexPath.row > maximumIndexPath.row)) maximumIndexPath = indexPath;
}
// build array of imageURLs for cells to prefetch
NSMutableArray *imageURLs = [NSMutableArray array];
indexPaths = [self tableView:tableView priorIndexPathCount:kPrefetchRowCount fromIndexPath:minimumIndexPath];
for (NSIndexPath *indexPath in indexPaths)
[imageURLs addObject:[self urlForIndexPath:indexPath]];
indexPaths = [self tableView:tableView nextIndexPathCount:kPrefetchRowCount fromIndexPath:maximumIndexPath];
for (NSIndexPath *indexPath in indexPaths)
[imageURLs addObject:[self urlForIndexPath:indexPath]];
// now prefetch
if ([imageURLs count] > 0)
{
[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:imageURLs];
}
}
これらは、NSIndexPath
可視セルの直前の行と可視セルの直後の行を取得するためのユーティリティ メソッドです。
/** Retrieve NSIndexPath for a certain number of rows preceding particular NSIndexPath in the table view.
*
* @param tableView The tableview for which we're going to retrieve indexPaths.
* @param count The number of rows to retrieve
* @param indexPath The indexPath where we're going to start (presumably the first visible indexPath)
*
* @return An array of indexPaths.
*/
- (NSArray *)tableView:(UITableView *)tableView priorIndexPathCount:(NSInteger)count fromIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger row = indexPath.row;
NSInteger section = indexPath.section;
for (NSInteger i = 0; i < count; i++) {
if (row == 0) {
if (section == 0) {
return indexPaths;
} else {
section--;
row = [tableView numberOfRowsInSection:section] - 1;
}
} else {
row--;
}
[indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
return indexPaths;
}
/** Retrieve NSIndexPath for a certain number of following particular NSIndexPath in the table view.
*
* @param tableView The tableview for which we're going to retrieve indexPaths.
* @param count The number of rows to retrieve
* @param indexPath The indexPath where we're going to start (presumably the last visible indexPath)
*
* @return An array of indexPaths.
*/
- (NSArray *)tableView:(UITableView *)tableView nextIndexPathCount:(NSInteger)count fromIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger row = indexPath.row;
NSInteger section = indexPath.section;
NSInteger rowCountForSection = [tableView numberOfRowsInSection:section];
for (NSInteger i = 0; i < count; i++) {
row++;
if (row == rowCountForSection) {
row = 0;
section++;
if (section == [tableView numberOfSections]) {
return indexPaths;
}
rowCountForSection = [tableView numberOfRowsInSection:section];
}
[indexPaths addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
return indexPaths;
}
そこにはたくさんありますが、実際にはSDWebImage
、それSDWebImagePrefetcher
は大変な作業を行っています。
完全を期すために、元の回答を以下に含めます。
元の答え:
でプリフェッチを行いたい場合はSDWebImage
、次のようにすることができます。
setImageWithURL
呼び出しに完了ブロックを追加します。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"%s", __FUNCTION__);
static NSString *cellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
TableModelRow *rowData = self.objects[indexPath.row];
cell.textLabel.text = rowData.title;
[cell.imageView setImageWithURL:rowData.url
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[self prefetchImagesForTableView:tableView];
}];
return cell;
}
ここで自分のルーチンを呼び出すのはあまり好きではないことを告白しなければなりませんprefetcher
(iOS に適切なdidFinishTableRefresh
デリゲート メソッドがあればいいのにと思います)。以下のルーチンが冗長なリクエストを作成しないことを確認するだけです。
とにかく、たとえば、次の 10 枚の画像を探すプリフェッチ ルーチンを作成します。
const NSInteger kPrefetchRowCount = 10;
- (void)prefetchImagesForTableView:(UITableView *)tableView
{
// determine the minimum and maximum visible rows
NSArray *indexPathsForVisibleRows = [tableView indexPathsForVisibleRows];
NSInteger minimumVisibleRow = [indexPathsForVisibleRows[0] row];
NSInteger maximumVisibleRow = [indexPathsForVisibleRows[0] row];
for (NSIndexPath *indexPath in indexPathsForVisibleRows)
{
if (indexPath.row < minimumVisibleRow) minimumVisibleRow = indexPath.row;
if (indexPath.row > maximumVisibleRow) maximumVisibleRow = indexPath.row;
}
// now iterate through our model;
// `self.objects` is an array of `TableModelRow` objects, one object
// for every row of the table.
[self.objects enumerateObjectsUsingBlock:^(TableModelRow *obj, NSUInteger idx, BOOL *stop) {
NSAssert([obj isKindOfClass:[TableModelRow class]], @"Expected TableModelRow object");
// if the index is within `kPrefetchRowCount` rows of our visible rows, let's
// fetch the image, if it hasn't already done so.
if ((idx < minimumVisibleRow && idx >= (minimumVisibleRow - kPrefetchRowCount)) ||
(idx > maximumVisibleRow && idx <= (maximumVisibleRow + kPrefetchRowCount)))
{
// my model object has method for initiating a download if needed
[obj downloadImageIfNeeded];
}
}];
}
ダウンロード ルーチンでは、イメージのダウンロードが開始されたかどうかを確認し、開始されていない場合は開始します。でこれを行うために、クラス (テーブルの個々の行をサポートするモデル クラス)で Web イメージ操作へのポインターSDWebImage
を保持します。weak
TableModelRow
@property (nonatomic, weak) id<SDWebImageOperation> webImageOperation;
次に、downloadImageIfNeeded
まだダウンロードしていない場合は、ルーチンにダウンロードを開始させます (作成weak
が非常に重要である理由がわかります...別の行を開始する前に、この行に保留中の操作が既にあるかどうかを確認しています)。私はダウンロードしたイメージで何もしていません (デバッグ目的で、ダウンロードが行われたという事実をログに記録することを除いて) ではなくSDImageWeb
、キャッシュされたイメージをダウンロードして追跡するだけなので、cellForRowAtIndexPath
後でイメージを次のように要求するときユーザーが下にスクロールすると、そこにあり、準備ができて待機しています。
- (void)downloadImageIfNeeded
{
if (self.webImageOperation)
return;
SDWebImageManager *imageManager = [SDWebImageManager sharedManager];
self.webImageOperation = [imageManager downloadWithURL:self.url
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
NSLog(@"%s: downloaded %@", __FUNCTION__, self.title);
// I'm not going to do anything with the image, but `SDWebImage` has now cached it for me
}];
}
imageManager.imageCache
最初にインスタンス メソッドを呼び出す方がより堅牢であると考えている部分もありますqueryDiskCacheForKey
が、いくつかのテストを行った後、それは必要ないように見えます (downloadWithURL
とにかく、私たちのためにそれを行います)。
SDImageWeb
ライブラリにはSDWebImagePrefetcher
クラスがあることを指摘しておく必要があります (ドキュメントを参照してください)。クラスの名前は信じられないほど有望ですが、コードを見ると、他の点では優れたライブラリにすべての敬意を払っているため、これはあまり堅牢ではないように感じます (たとえば、フェッチする URL の単純なリストであり、もう一度実行すると、「キューに追加する」などの概念なしで前のリストをキャンセルします)。それは有望な概念ですが、実行には少し弱いです。そして試してみると、UXが著しく低下しました。
したがって、私はSDWebImagePrefetcher
(少なくとも改善されるまでは) 使用しない傾向があり、初歩的なプリフェッチ手法に固執します。それほど洗練されたものではありませんが、機能しているようです。