与GCD重用UITableViewCell

我正在使用Grand Central Dispatch来asynchronous加载UITableViewCell图像。 除了在某些边框情况下单元格被重用,以及之前的块加载错误的图像之外,这种方法效果很好。

我目前的代码如下所示:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } NSString *imagePath = [self imagePathForIndexPath:indexPath]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; dispatch_sync(dispatch_get_main_queue(), ^{ cell.imageView.image = image; [cell setNeedsLayout]; }); }); return cell; } 

据我所知,GCD队列不能停止。 那么这个边界案件如何被阻止呢? 或者我应该使用别的东西而不是GCD来解决这个问题?

我所做的是添加NSOperation伊娃到单元格。 该操作负责获取,加载和创build图像。 当完成时,它ping单元格。 当/如果单元格出队时,操作被取消,如果没有完成则被销毁。 -main在取消时的操作testing。 图像传递到单元格后,单元格将会丢弃该操作并使用该图像。

在开始asynchronous请求之前,您可能会尝试强制重置图像。

当用户滚动表格时,他会在你的asynchronous方法用正确的方法改变它之前看到旧的图像

只需添加这一行:

 cell.imageView.image = nil; // or a placeHolder image dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; dispatch_sync(dispatch_get_main_queue(), ^{ cell.imageView.image = image; [cell setNeedsLayout]; }); }); 

编辑:

@hgpc:

我不认为这解决了这个问题。 之前的块仍然可以在新块之前设置错误的图像。 – hgpc 9分钟前

你是对的,如果用户滚动快这可能是一个问题…

我看到的唯一方法是使一个计数器属性的自定义单元存储(以同步的方式)在每个单元队列中的操作数,然后在asynchronous方法检查计数器是== 1之前改变图像:

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { // MyCustomUITableViewCell has a property counter cell = [[[MyCustomUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; cell.counter = 0; } NSString *imagePath = [self imagePathForIndexPath:indexPath]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); cell.imageView.image = nil; // or a placeHolder image cell.counter = cell.counter + 1; dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; dispatch_sync(dispatch_get_main_queue(), ^{ if (cell.counter == 1){ cell.imageView.image = image; [cell setNeedsLayout]; } cell.counter = cell.counter - 1; }); }); return cell; } 

您可以在启动asynchronous任务之前设置一个单元标识符,并在更新UI之前检查它是否相同。

你在这里有几个可能性。

a)使用NSOperationQueue而不是直接的GDC。
NSOperation基于GDC,并启用了许多pipe理,如停止线程。
看看苹果的并发指南 。

b)使用一个准备好的asynchronous图像加载器。
internetz上有足够的免费和开源项目。
我个人推荐AFNetworking (特别是AFNetworking+UIImageView类别)。

如果你想自己做(研究,知识等),你应该留在a) ,但更方便的方法是b)

更新
我只是注意到,你正在从文件加载图像,而不是从networking。
在这种情况下,您应该采取不同的处理方式,并注意以下几点:

  • 避免imageWithContentsOfFile:而是使用imageNamed: imageWithContentsOfFile: 这是因为imageNamed:caching你的图像一旦加载和imageWithContentsOfFile:不。 如果您需要使用imageWithContentsOfFile:预先加载imageWithContentsOfFile:所有图像,并使用此caching中的图像填充表格。

  • TableView图像的尺寸不应该很大(尺寸文件大小)。 即使是一个100×100像素的视网膜图像也没有超过几个kb,这绝对不会花费这么长的时间来加载,你需要一个asynchronous加载。

那么只有在表格不滚动时才加载图片呢? cellForRowWithIndexPath:的问题是:你正在为滚动的单元格做很多工作。

你可以在viewDidLoad (或者你的数据模型初始化的时候)和scrollViewDidEndDecelerating:scrollViewDidEndDecelerating:

 -(void)viewDidLoad { [super viewDidLoad]; [self initializeData]; [self loadVisibleImages]; } -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if (scrollView == self.tableView) { [self loadVisibleImages]; } } -(void)loadVisibleImages { for (NSIndexPath *indexPath in [self.tableview indexPathsForVisibleRows]) { dispatch_async(queue, ^{ NSString *imagePath = [self imagePathForIndexPath:indexPath];    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];    dispatch_sync(dispatch_get_main_queue(), ^{ UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];      cell.imageView.image = image;      [cell setNeedsLayout];    });  }); } }