5

我正在使用Grand Central DispatchUITableViewCell异步加载图像。这很好用,除了在某些边界情况下,单元格被重用,并且前一个块加载了错误的图像。

我当前的代码如下所示:

- (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 来解决这个问题?

4

5 回答 5

3

我所做的是NSOperation在单元格中添加了一个 ivar。该操作负责获取、加载和创建图像。完成后,它会 ping 单元格。当/如果单元出队时,操作将被取消并在未完成时销毁。中的取消操作测试-main。图像传递到单元格后,单元格放弃操作并使用图像。

于 2012-08-13T09:28:52.027 回答
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;
}
于 2012-08-13T08:42:30.337 回答
0

您可以在启动异步任务之前设置一个单元格标识符,并在更新 UI 之前检查它是否相同。

于 2012-08-13T09:06:54.853 回答
0

这里有几种可能性。

a) 使用NSOperationQueue而不是直接 GDC。
NSOperation 基于 GDC 并启用了许多管理,例如停止线程。
看看苹果的并发指南

b) 使用现成的异步图像加载器。
internetz 上有足够多的免费和开源项目。
我个人推荐AFNetworking(尤其是AFNetworking+UIImageView类别)。

如果你想自己做(研究、知识等),你应该坚持a),但更方便的方法是b)

更新
我刚刚注意到,您正在从文件而不是从网络加载图像。
在这种情况下,您应该以不同的方式处理它,并注意以下几点:

  • 避免imageWithContentsOfFile:而是使用imageNamed:. 这是因为imageNamed:一旦加载imageWithContentsOfFile:而不加载图像就会缓存您的图像。如果您需要使用imageWithContentsOfFile:预加载 NSCache 中的所有图像并使用此缓存中的图像填充表。

  • TableView 图像不应太大(尺寸文件大小)。即使是 100x100 像素的视网膜图像也不会超过几个 kb,加载时间绝对不会太长,以至于您需要异步加载。

于 2012-08-13T09:37:18.420 回答
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];
            });
        });
    }
}
于 2012-08-14T06:22:17.843 回答