Iphone 使用GCD重用UITableViewCell
我使用异步加载Iphone 使用GCD重用UITableViewCell,iphone,ios,uitableview,grand-central-dispatch,Iphone,Ios,Uitableview,Grand Central Dispatch,我使用异步加载UITableViewCell的图像。这很有效,但在某些边界情况下除外,在这些情况下,单元被重用,并且前一个块加载了错误的图像 我当前的代码如下所示: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableVie
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来解决此问题?在启动异步请求之前,您可以尝试强制重置映像 当用户滚动表格时,他可能会在异步方法更改为正确的图像之前看到旧图像 只需添加这一行:
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分钟前 你是对的,如果用户快速滚动,这可能是一个问题 我看到的唯一方法是创建一个带有计数器属性的自定义单元格,其中存储(以同步方式)每个单元格队列中的操作数n,然后在异步方法中检查计数器是否为==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;
}
您可以在启动异步任务之前设置单元格标识符,并在更新UI之前检查其是否相同。我所做的是向单元格添加一个
NSOperation
ivar。该操作负责获取、加载和创建图像。完成后,将ping单元格。当/如果单元退出队列,操作将被取消,如果尚未完成,操作将被销毁。在-main
中进行取消操作测试。图像传递到单元格后,单元格放弃操作并使用图像。这里有几种可能性
a) 使用NSOperationQueue
代替直接GDC。NSOperation基于GDC并启用了许多管理,如停止线程。
看看苹果公司的产品 b) 使用现成的异步映像加载程序。
internetz上有足够的免费开源项目。
我个人推荐
AFNetworking
(尤其是AFNetworking+UIImageView
类别)
如果你想自己做(研究、知识等),你应该选择a),但更方便的方法是b)
更新我刚刚注意到,您正在从文件而不是网络加载图像。
在这种情况下,您应该以不同的方式处理,并注意以下几点:
- 避免使用
,而是使用imageWithContentsOfFile:
。这是因为imageNamed:
一旦将图像加载到哪里,就会将其缓存,而imageNamed:
则不会。如果需要使用imageWithContentsOfFile:
将所有图像预加载到NSCache中,并使用此缓存中的图像填充表imageWithContentsOfFile:
- TableView图像的大小不应太大(维度和文件大小)。即使是一个100x100像素的视网膜图像也不会超过几kb,这肯定不会花费太长的时间来加载,因此需要异步加载
cellForRowWithIndexPath:
的问题在于,您正在为滚动过去的单元格做大量工作
您可以在viewDidLoad
(或在初始化数据模型时)和scrollviewdidenddeclaring:
上执行此操作,而不是在单元格出列时加载图像
-(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];
});
});
}
}
我认为这不能解决问题。以前的块仍然可以在新块之前设置错误的图像。现在,计数器确保只设置了一个图像,但不一定是正确的图像。根据@willy的建议,我认为您可以简单地使用indexPath设置视图标记,并在更改图像之前检查是否相同。嗯,我猜队列按照您将它们放入队列的顺序开始操作,所以最后一个是正确的。。。对于willy answer,我不确定在异步方法中您是否仍然可以检查indexPath是否正确,它可能存储旧的indexPath,即操作启动时单元格拥有的indexPath。。。你可以做个测试。。。让我知道它是否有效,我真的很高兴你是对的。由于这是一个全局队列,将按顺序处理块。将indexath.row存储在cell标记中,然后检查是否相同。不过有点老土。我用GMGridView显示了大量的图像缩略图,并且有着完全相同的问题。我还使用GCD动态地从我的Web服务器加载缩略图。应该注意的是,图像是从文件加载的,这就是我想要使其异步的延迟。不涉及下载。啊,我明白了,是的。但在这种情况下,你还有其他问题。我很快就会给出我的答案。NSOperation肯定比GCD好,通常是使用它,而且将它放在单元格中肯定是最好(也是最优雅)的方式+1.