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:
    则不会。如果需要使用
    imageWithContentsOfFile:
    将所有图像预加载到NSCache中,并使用此缓存中的图像填充表

  • 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.