在重用UITableViewCell的同时使用SDWebImage处理图像的下载

我已经使用第三方库SDWebImage为我的UITableView单元格下载图像,在单元格内创buildUIImageView并触发请求,同时configuration像这样的单元格。

[imageView setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:[UIImage imageNamed:@"default.jpg"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) { }]; 

它的工作正常,但是当我滚动快,大部分图像没有完全下载(我可以看到,在查尔斯),因为该图像没有caching。 即使我的单元被重用,我如何caching已经发送的请求,以便相同的请求不会多次。

请忽略任何错字:)

有效的iOS 10,我原来的答案手动预取代码不再需要。 只需设置一个prefetchDataSource 。 例如,在Swift 3:

 override func viewDidLoad() { super.viewDidLoad() tableView.prefetchDataSource = self } 

然后有一个prefetchRowsAtIndexPaths使用SDWebImagePrefetcher来获取行

 extension ViewController: UITableViewDataSourcePrefetching { public func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { let urls = indexPaths.map { baseURL.appendingPathComponent(images[$0.row]) } SDWebImagePrefetcher.shared().prefetchURLs(urls) } } 

你可以有标准的cellForRowAt

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let url = baseURL.appendingPathComponent(images[indexPath.row]) cell.imageView?.sd_setImage(with: url, placeholderImage: placeholder) return cell } 

就个人而言,我更喜欢AlamofireImage。 所以UITableViewDataSourcePrefetching略有不同

 extension ViewController: UITableViewDataSourcePrefetching { public func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { let requests = indexPaths.map { URLRequest(url: baseURL.appendingPathComponent(images[$0.row])) } AlamofireImage.ImageDownloader.default.download(requests) } } 

显然, cellForRowAt会使用af_setImage

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let url = baseURL.appendingPathComponent(images[indexPath.row]) cell.imageView?.af_setImage(withURL: url, placeholderImage: placeholder) return cell } 

下面我的原始答案显示了对于Objective-C,如何在10之前的iOS版本(我们必须执行自己的预取计算)中执行此操作。


取消下载不再可见的单元格的行为正是当您快速滚动时,即使使用较慢的Internet连接,也能保持asynchronous图像检索的响应。 例如,如果您快速向下滚动到tableview的第100个单元格,则实际上不希望该图像检索在前面99行(不再可见)的图像检索后面积压。 我build议离开UIImageView类别,而是使用SDWebImagePrefetcher如果你想预取图像的细胞,你可能会滚动到。

例如,在我称之为reloadData ,我还预取了当前可见单元格之前和之后的十个单元格的图像:

 [self.tableView reloadData]; dispatch_async(dispatch_get_main_queue(), ^{ [self prefetchImagesForTableView:self.tableView]; }); 

同样,任何时候我停止滚动,我也是这样做的:

 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self prefetchImagesForTableView:self.tableView]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) [self prefetchImagesForTableView:self.tableView]; } 

就我如何做十个前后单元的预取而言,我是这样做的:

 #pragma mark - Prefetch cells static NSInteger const kPrefetchRowCount = 10; /** 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 ([minimumIndexPath compare:indexPath] == NSOrderedDescending) minimumIndexPath = indexPath; if ([maximumIndexPath compare:indexPath] == NSOrderedAscending) maximumIndexPath = indexPath; } // build array of imageURLs for cells to prefetch NSMutableArray<NSIndexPath *> *prefetchIndexPaths = [NSMutableArray array]; NSArray<NSIndexPath *> *precedingRows = [self tableView:tableView indexPathsForPrecedingRows:kPrefetchRowCount fromIndexPath:minimumIndexPath]; [prefetchIndexPaths addObjectsFromArray:precedingRows]; NSArray<NSIndexPath *> *followingRows = [self tableView:tableView indexPathsForFollowingRows:kPrefetchRowCount fromIndexPath:maximumIndexPath]; [prefetchIndexPaths addObjectsFromArray:followingRows]; // build array of imageURLs for cells to prefetch (how you get the image URLs will vary based upon your implementation) NSMutableArray<NSURL *> *urls = [NSMutableArray array]; for (NSIndexPath *indexPath in prefetchIndexPaths) { NSURL *url = self.objects[indexPath.row].imageURL; if (url) { [urls addObject:url]; } } // now prefetch if ([urls count] > 0) { [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:urls]; } } /** 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<NSIndexPath *> *)tableView:(UITableView *)tableView indexPathsForPrecedingRows:(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<NSIndexPath *> *)tableView:(UITableView *)tableView indexPathsForFollowingRows:(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; } 

导致下载被取消的行在UIImageView+WebCache.m

第一行- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock

调用[self cancelCurrentImageLoad]; 。 如果你摆脱这一行,下载操作应继续进行。

现在的问题是,如果一个UIImageView等待多个下载完成,就会出现竞争状态。 最后一次下载完成可能不一定是最后一次启动,所以你最终可能会在你的单元格中显示错误的图像。

有几种方法可以解决这个问题。 最简单的可能就是在UIImageView+WebCache添加另一个关联的对象,只是最后一个加载的URL。 然后,您可以在完成块中检查此URL,并只在匹配时设置图像。

我遇到了同样的问题,我发现UIImageView + WebCache取消了最后一次下载时的新下载。

我不确定这是否是作者的意图。 所以我写了一个基于SDWebImage的新的UIImageView category

要查看更多: ImageDownloadGroup

使用方便:

 [cell.imageView mq_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] groupIdentifier:@"customGroupID" completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { }];