正确的方式来处理与后台线程细胞再利用?(Proper way to deal with cell r

2019-07-05 14:46发布

我有一个UICollectionView ,但同样的方法应该适用于UITableViews 。 我的每一个细胞的包含一个图像I从磁盘,它是一个缓慢的操作负担。 为了缓解这个问题,我使用异步调度队列。 这工作得很好,但在这些操作中快速滚动的效果叠加起来,使得细胞将其形象从一个到另一个顺序改变,直到最后的最后一次通话停止。

在过去,我已经做了检查,看是否牢房仍清晰可见,如果不是我不要继续。 然而,这不符合UICollectionView工作,反正它是没有效率的。 我正在考虑迁移到使用NSOperations ,它可以抵消,使得只有最后呼吁修改单元格会经过。 我可以通过检查操作已在完成可能做到这一点prepareForReuse方法,如果不是取消它。 我希望有人已经处理了这个问题,在过去,并能提供一些提示或解决方案。

Answer 1:

会话211 - iOS上构建并发用户界面,从2012年WWDC ,讨论了小区的形象改变,因为后台任务赶上(起始于38m15s)的问题。

这里是你如何解决这个问题。 已加载背景排队的图像后,使用索引的路径来寻找当前单元格,如果有的话,那就是显示包含图像的项目。 如果你得到一个单元格,设置单元格的图像。 如果你为零,还有目前正显示项目,以便只是丢弃图像无细胞。

- (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实例变量:

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

当你需要加载图像,放在集内的小区的索引路径和调度,它有一个索引路径出了一套和加载其图像的任务:

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

如果停止显示单元,请从集内的小区的索引路径:

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

要加载图像,您可以通过设置索引路径。 我们必须派遣回这个主队列,以避免在组中的竞争条件:

- (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:方法与上述相同。 但是,当我们真正在小区集中的形象,我们也希望从集合中删除单元格的索引路径:

- (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];
}


Answer 2:

你是否尝试过使用SDWebImage开源框架,从网络上下载图像和异步缓存他们的作品? 它的工作原理绝对伟大的UITableViews ,并应与良好UICollectionViews 。 你只需要使用setImage:withURL:方法的UIImageView扩展,它的魔力。



Answer 3:

我有同样的问题,约400元的UITableView中,发现一个残酷的,但工作的解决方案:不要重复使用细胞! 取决于有多少和多大的图像,但实际上iOS版重新分配未使用的电池非常有效......还是那句话:是不是纯粹的将建议,但夫妇与异步加载是照明快速,无差错而不必处理复杂的同步逻辑。



文章来源: Proper way to deal with cell reuse with background threads?