Если вы хотите выполнить предварительную выборку строк, вы можете ответить на 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
(по крайней мере, пока он не будет улучшен) и придерживаться своей элементарной техники предварительной выборки. Это не очень сложно, но, похоже, работает.
person
Rob
schedule
29.03.2013
SDWebImage
имеют все интерфейсы для прямого взаимодействия сSDWebImageManager
напрямую. Вы можете реализовать свою собственную процедуру предварительной выборки, но будьте осторожны при обработкеdidReceiveMemoryWarning
. Возможно, вы также захотите тщательно управлять своим кешем, возможно, только с предварительной выборкой некоторого ограниченного числа предстоящих ячеек. Не многие из этих классов имеют встроенные процедуры предварительной выборки, поскольку это противоположно тому, что пытается сделать большинство людей. Но умная предварительная выборка — хорошая идея. Не слишком сложно, но и не тривиально. У вас есть хорошая модель, поддерживающая ваше табличное представление? - person Rob   schedule 29.03.2013