使用后台线程处理单元重用的正确方法? [英] Proper way to deal with cell reuse with background threads?

查看:100
本文介绍了使用后台线程处理单元重用的正确方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 UICollectionView ,但相同的方法应该适用于 UITableViews 。我的每个单元格都包含一个从磁盘加载的图像,这是一个缓慢的操作。为了缓解这种情况,我使用异步调度队列。这样可以正常工作,但是快速滚动导致这些操作堆叠起来,这样单元格将按顺序将其图像从一个更改为另一个,直到最后一次调用停止。

I have a UICollectionView, but the same methods should apply to UITableViews. Each of my cells contains an image I load from disk, which is a slow operation. To mitigate this, I use an async dispatch queue. This works fine, but quickly scrolling results in these operations stacking up, such that the cell will change its image from one to another in sequence, until finally stopping at the last call.

过去,我已经检查了细胞是否仍然可见,如果不是,我不会继续。但是,这不适用于UICollectionView,无论如何它效率不高。我正在考虑迁移使用 NSOperations ,这可以取消,这样只有最后一次修改单元格的调用才能通过。我可以通过检查操作是否已在 prepareForReuse 方法中完成,如果没有则取消它。我希望有人在过去处理过这个问题并提供一些提示或解决方案。

In the past, I've done a check to see if the cell was still visible, and if not I don't continue. However, this is not working with UICollectionView, and anyway it is not efficient. I am considering migrating to use NSOperations, which can be cancelled, so that only the last call to modify the cell will go through. I could possibly do this by checking if the operation had finished in the prepareForReuse method, and cancelling it if not. I'm hoping someone has dealt with this issue in the past and can provide some tips or a solution.

推荐答案

会话211 - 在iOS上构建并发用户界面,来自WWDC 2012 ,讨论了问题当背景任务赶上时(从38m15开始),细胞图像的变化。

Session 211 - Building Concurrent User Interfaces on iOS, from WWDC 2012, discusses the problem of the cell's image changing as the background tasks catch up (starting at 38m15s).

以下是解决该问题的方法。在背景队列上加载图像后,使用索引路径查找显示包含该图像的项目的当前单元格(如果有)。如果您有一个单元格,请设置单元格的图像。如果你得到nil,那么当前没有单元格显示该项目,所以只丢弃图像。

Here's how you solve that problem. After you've loaded the image on the background queue, use the index path to find the current cell, if any, that's displaying the item containing that image. If you get a cell, set the cell's image. If you get nil, there's no cell currently displaying that item so just discard the image.

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    cell.imageView.image = nil;
    dispatch_async(myQueue, ^{
        [self bg_loadImageForRowAtIndexPath:indexPath];
    });
    return cell;
}

- (void)bg_loadImageForRowAtIndexPath:(NSIndexPath *)indexPath {
    // I assume you have a method that returns the path of the image file.  That
    // method must be safe to run on the background queue.
    NSString *path = [self bg_pathOfImageForItemAtIndexPath:indexPath];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    // Make sure the image actually loads its data now.
    [image CGImage];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self setImage:image forItemAtIndexPath:indexPath];
    });
}

- (void)setImage:(UIImage *)image forItemAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = (MyCell *)[collectionView cellForItemAtIndexPath:indexPath];
    // If cell is nil, the following line has no effect.
    cell.imageView.image = image;
}

这是一种简单的方法来减少加载图像的后台任务的堆积视图不再需要。给自己一个 NSMutableSet 实例变量:

Here's an easy way to reduce the "stacking up" of background tasks loading images that the view no longer needs. Give yourself an NSMutableSet instance variable:

@implementation MyViewController {
    dispatch_queue_t myQueue;
    NSMutableSet *indexPathsNeedingImages;
}

当您需要加载图像时,请将单元格的索引路径放入设置和调度一个任务,该任务从集合中取出一个索引路径并加载其图像:

When you need to load an image, put the index path of the cell in the set and dispatch a task that takes one index path out of the set and loads its image:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    cell.imageView.image  = nil;
    [self addIndexPathToImageLoadingQueue:indexPath];
    return cell;
}

- (void)addIndexPathToImageLoadingQueue:(NSIndexPath *)indexPath {
    if (!indexPathsNeedingImages) {
        indexPathsNeedingImages = [NSMutableSet set];
    }
    [indexPathsNeedingImages addObject:indexPath];
    dispatch_async(myQueue, ^{ [self bg_loadOneImage]; });
}

如果单元格停止显示,请从集合中删除单元格的索引路径:

If a cell stops being displayed, remove the cell's index path from the set:

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
    [indexPathsNeedingImages removeObject:indexPath];
}

要加载图像,请从集合中获取索引路径。我们必须为此发送回主队列,以避免集合上的竞争条件:

To load an image, get an index path from the set. We have to dispatch back to the main queue for this, to avoid a race condition on the set:

- (void)bg_loadOneImage {
    __block NSIndexPath *indexPath;
    dispatch_sync(dispatch_get_main_queue(), ^{
        indexPath = [indexPathsNeedingImages anyObject];
        if (indexPath) {
            [indexPathsNeedingImages removeObject:indexPath];
        }
    }

    if (indexPath) {
        [self bg_loadImageForRowAtIndexPath:indexPath];
    }
}

bg_loadImageForRowAtIndexPath:方法与上面相同。但是当我们在单元格中实际设置图像时,我们还想从集合中删除单元格的索引路径:

The bg_loadImageForRowAtIndexPath: method is the same as above. But when we actually set the image in the cell, we also want to remove the cell's index path from the set:

- (void)setImage:(UIImage *)image forItemAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = (MyCell *)[collectionView cellForItemAtIndexPath:indexPath];
    // If cell is nil, the following line has no effect.
    cell.imageView.image = image;

    [indexPathsNeedingImages removeObject:indexPath];
}

这篇关于使用后台线程处理单元重用的正确方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆