Se si desidera eseguire il prefetch delle righe, è possibile rispondere ai metodi UIScrollViewDelegate
per determinare quando lo scorrimento della tabella è terminato, attivando un prefetch delle righe. È possibile eseguire il precaricamento utilizzando SDWebImagePrefetcher
(nella mia risposta iniziale ero un po 'sprezzante di questa classe utile, ma sembra funzionare relativamente bene ora):
- (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
Ecco la semplice cellForRowAtIndexPath
(non del tutto pertinente, ma solo mostrando che se si utilizza SDWebImagePrefetcher
, non c'è bisogno di pasticciare con 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;
}
Questi metodi UIScrollViewDelegate
prefetch più righe durante lo scorrimento finiture
- (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];
}
Ovviamente è necessario implementare una routine di prefetch. Ciò ottiene i valori NSIndexPath
per le celle su ciascun lato delle celle visibili, ottiene i relativi URL immagine e quindi esegue il prefetch su tali dati.
/** 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];
}
}
Questi sono i metodi di utilità per ottenere il NSIndexPath
per le righe immediatamente precedenti le celle visibili e quelli immediatamente successivi le celle visibili:
/** 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;
}
C'è molto lì, ma in realtà, SDWebImage
e il suo SDWebImagePrefetcher
sta facendo il sollevamento pesi.
Includo la mia risposta originale di seguito per motivi di completezza.
risposta originale:
Se si vuole fare un po 'prefetching con SDWebImage
, si potrebbe fare qualcosa di simile al seguente:
Aggiungere un blocco di completamento al vostro setImageWithURL
chiamata:
- (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;
}
Devo confessare che non mi piace molto chiamare la mia routine prefetcher
qui (vorrei che iOS avesse un buon metodo delegato didFinishTableRefresh
), ma funziona, anche se chiama la routine più volte di quanto vorrei davvero. Mi limito solo a verificare che la routine seguente assicuri che non crei richieste ridondanti.
Comunque, ho scrivere una routine prefetch che cerca, per esempio, i prossimi dieci immagini:
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];
}
}];
}
Nella routine di download, è possibile controllare per vedere se l'immagine download è avviato e, se no, quindi avvialo. Per fare questo con SDWebImage
, ho mantenere un puntatore weak
per l'operazione di immagine Web nel mio TableModelRow
classe (la classe del modello che sostiene le singole righe del mio tavolo):
@property (nonatomic, weak) id<SDWebImageOperation> webImageOperation;
Ho poi hanno la downloadImageIfNeeded
routine di avviare un download se non lo ha già fatto (puoi capire perché è stato così importante fare weak
... Sto controllando per vedere se questa riga ha già un'operazione in sospeso prima di iniziarne un'altra). Non sto facendo nulla con l'immagine scaricata (a corto di, a scopo di debug, registrando il fatto che è stato fatto un download), ma piuttosto semplicemente scaricando e lasciando che SDImageWeb
tenga traccia dell'immagine in cache per me, quindi quando cellForRowAtIndexPath
richiede successivamente il immagine mentre l'utente scorre verso il basso, è lì, pronto e in attesa.
- (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
}];
}
Una parte di me pensa che potrebbe essere più robusta di chiamare imageManager.imageCache
metodo di istanza queryDiskCacheForKey
prima, ma dopo aver fatto alcune prove, ma non sembra che è necessario (e la downloadWithURL
fa per noi, comunque).
Tengo a precisare che la biblioteca SDImageWeb
ha una classe SDWebImagePrefetcher
(vedi the documentation). Il nome della classe è incredibilmente promettente, ma guardando il codice, con tutto il rispetto per una libreria altrimenti eccellente, questo non mi sembra molto robusto (ad esempio è un semplice elenco di URL da recuperare e se lo fai di nuovo , annulla la lista precedente senza alcuna nozione di "aggiunta alla coda" o qualcosa del genere). È un'idea promettente, ma un po 'debole nell'esecuzione. E quando l'ho provato, la mia UX ha sofferto notevolmente.
Quindi, sono propenso a non utilizzare SDWebImagePrefetcher
(fino a quando non viene migliorato, almeno) e ad attenermi alla mia tecnica di prefetching rudimentale. Non è terribilmente sofisticato, ma sembra funzionare.
Sei sicuro di quelle cellule sono la creazione di che sono fuori dallo schermo? Conosci il comportamento di UITableView? – Exploring
Le celle vengono riutilizzate – user2082760
Sì, quindi quali celle sono disattivate, quindi il metodo di istanza verrà chiamato per quelle celle? Non è il problema di SDWebImage. – Exploring