与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]; }); }); } }