Ответ 1
Если вы хотите предварительно выбрать строки, вы можете ответить на методы 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
), но он работает, даже если он вызывает процедуру больше раз, чем я действительно хотеть. Я просто убедился ниже, что нижеприведенная процедура гарантирует, что он не будет делать избыточные запросы. -
В любом случае, я пишу процедуру предварительной выборки, которая ищет, скажем, следующие десять изображений:
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]; } }]; }
-
В процедуре загрузки вы можете проверить, запущена ли загрузка изображений, а если нет, запустите ее. Чтобы сделать это с помощью
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
(пока он не улучшится, по крайней мере), и придерживаться моей рудиментарной техники предварительной выборки. Это не очень сложно, но, похоже, это работает.