重用UITableViewCell与GCD

时间:2012-08-13 08:32:37

标签: iphone ios uitableview grand-central-dispatch

我正在使用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来解决这个问题?

5 个答案:

答案 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];
            });
        });
    }
}