我正在使用Grand Central Dispatch异步加载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来解决这个问题?
答案 0 :(得分:3)
我所做的是向细胞添加NSOperation
ivar。该操作负责获取,加载和创建映像。完成后,它会ping单元格。当/如果单元格出列时,操作将被取消并在未完成时销毁。在-main
中取消的操作测试。将图像传递到单元格后,单元格将删除操作并使用图像。
答案 1 :(得分:1)
您可以尝试在启动异步请求之前强制重置映像。
当用户滚动表格时,他可能会在您的异步方法使用正确的方法更改之前看到旧图像
只需添加以下行:
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分钟前
你是对的,如果用户快速滚动,这可能是个问题......
我看到的唯一方法是创建一个具有计数器属性的自定义单元格,以存储(以同步方式)每个单元格的队列中的操作数,然后在异步方法中检查计数器是否== 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;
}
答案 2 :(得分:0)
您可以在启动异步任务之前设置单元格标识符,并在更新UI之前检查它是否相同。
答案 3 :(得分:0)
你有几种可能性。
a)使用NSOperationQueue
代替直接GDC
NSOperation基于GDC,并启用了许多管理,如停止线程
看看苹果的concurrency guide。
b)使用准备好的异步图像加载器
internetz上有足够的免费和开源项目。
我个人推荐AFNetworking
(特别是AFNetworking+UIImageView
类别)。
如果你想自己做(研究,知识等)你应该坚持 a),但更方便的方法是 b)。
<强>更新强>
我刚刚注意到,您正在从文件加载图像而不是从网络加载图像
在这种情况下,您应该以不同的方式处理它并注意以下几点:
避免使用imageWithContentsOfFile:
,而是使用imageNamed:
。这是因为imageNamed:
会在您加载的图像和imageWithContentsOfFile:
不加载图像时对其进行缓存。如果您需要使用imageWithContentsOfFile:
预加载NSCache中的所有图像,并使用此缓存中的图像填充表格。
TableView图像的大小不应太大(维度和 filesize)。即使是一个100x100像素的视网膜图像也不会超过几个kb,因此加载时你需要一个异步负载。
答案 4 :(得分:0)
当桌子不滚动时仅加载图像怎么样? cellForRowWithIndexPath:
的问题在于您正在为滚动过去的单元格做很多工作。
您可以在viewDidLoad
(或初始化数据模型时)以及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];
});
});
}
}