我有一个UICollectionView
,但同样的方法应该适用于UITableViews
。我的每个单元格都包含一个从磁盘加载的图像,这是一个很慢的操作。为了缓解这种情况,我使用异步调度队列。这样可以正常工作,但是快速滚动导致这些操作堆叠起来,这样单元格将按顺序将其图像从一个更改为另一个,直到最后一次调用停止。
过去,我已经检查了细胞是否仍然可见,如果不是,我不会继续。但是,这不适用于UICollectionView,无论如何它效率不高。我正在考虑迁移使用NSOperations
,这可以取消,这样只有最后一次修改单元格的调用才能通过。我可以通过检查操作是否在prepareForReuse
方法中完成,然后取消它来完成此操作。我希望过去有人处理过这个问题,并提供一些提示或解决方案。
答案 0 :(得分:21)
Session 211 - Building Concurrent User Interfaces on iOS, from WWDC 2012,讨论了当背景任务赶上时(从38m15开始)细胞图像变化的问题。
以下是您解决该问题的方法。将图像加载到后台队列后,使用索引路径查找显示包含该图像的项目的当前单元格(如果有)。如果您有一个单元格,请设置单元格的图像。如果你得到nil,那么当前没有单元格显示该项目,所以只丢弃图像。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
cell.imageView.image = nil;
dispatch_async(myQueue, ^{
[self bg_loadImageForRowAtIndexPath:indexPath];
});
return cell;
}
- (void)bg_loadImageForRowAtIndexPath:(NSIndexPath *)indexPath {
// I assume you have a method that returns the path of the image file. That
// method must be safe to run on the background queue.
NSString *path = [self bg_pathOfImageForItemAtIndexPath:indexPath];
UIImage *image = [UIImage imageWithContentsOfFile:path];
// Make sure the image actually loads its data now.
[image CGImage];
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:image forItemAtIndexPath:indexPath];
});
}
- (void)setImage:(UIImage *)image forItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = (MyCell *)[collectionView cellForItemAtIndexPath:indexPath];
// If cell is nil, the following line has no effect.
cell.imageView.image = image;
}
这是一种简单的方法,可以减少加载视图不再需要的图像的后台任务的“堆积”。给自己一个NSMutableSet
实例变量:
@implementation MyViewController {
dispatch_queue_t myQueue;
NSMutableSet *indexPathsNeedingImages;
}
当你需要加载图像时,将单元格的索引路径放在集合中,并调度一个任务,该任务将一个索引路径从集合中取出并加载其图像:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
cell.imageView.image = nil;
[self addIndexPathToImageLoadingQueue:indexPath];
return cell;
}
- (void)addIndexPathToImageLoadingQueue:(NSIndexPath *)indexPath {
if (!indexPathsNeedingImages) {
indexPathsNeedingImages = [NSMutableSet set];
}
[indexPathsNeedingImages addObject:indexPath];
dispatch_async(myQueue, ^{ [self bg_loadOneImage]; });
}
如果单元格停止显示,请从集合中删除单元格的索引路径:
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[indexPathsNeedingImages removeObject:indexPath];
}
要加载图像,请从集合中获取索引路径。我们必须为此调度回主队列,以避免集合上的竞争条件:
- (void)bg_loadOneImage {
__block NSIndexPath *indexPath;
dispatch_sync(dispatch_get_main_queue(), ^{
indexPath = [indexPathsNeedingImages anyObject];
if (indexPath) {
[indexPathsNeedingImages removeObject:indexPath];
}
}
if (indexPath) {
[self bg_loadImageForRowAtIndexPath:indexPath];
}
}
bg_loadImageForRowAtIndexPath:
方法与上述方法相同。但是当我们实际在单元格中设置图像时,我们还想从集合中删除单元格的索引路径:
- (void)setImage:(UIImage *)image forItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = (MyCell *)[collectionView cellForItemAtIndexPath:indexPath];
// If cell is nil, the following line has no effect.
cell.imageView.image = image;
[indexPathsNeedingImages removeObject:indexPath];
}
答案 1 :(得分:8)
您是否尝试过使用SDWebImage开源框架,该框架可以从网上下载图像并以异步方式缓存它们?它对UITableViews
非常有效,并且与UICollectionViews
一样好。您只需使用setImage:withURL:
扩展程序的UIImageView
方法,就可以实现神奇。
答案 2 :(得分:-2)
我遇到了大约400个元素的UITableView同样的问题,并找到了一个残酷但有效的解决方案:不要重复使用单元格!取决于图像的数量和大小,但实际上iOS在释放未使用的单元格方面非常有效... 再说一遍:不是纯粹主义者会建议的,但是对异步负载来说,快速点亮,没有错误,并且不必处理复杂的同步逻辑。