Objective c 使用后台线程处理单元重用的正确方法?

Objective c 使用后台线程处理单元重用的正确方法?,objective-c,multithreading,ios6,nsoperation,uicollectionview,Objective C,Multithreading,Ios6,Nsoperation,Uicollectionview,我有一个UICollectionView,但是同样的方法应该适用于uitableview。我的每个单元格都包含一个从磁盘加载的映像,这是一个缓慢的操作。为了缓解这种情况,我使用了一个异步调度队列。这样做很好,但快速滚动会导致这些操作堆积起来,这样单元格将按顺序从一个图像切换到另一个图像,直到最后在最后一次调用时停止 在过去,我做了一次检查,看看细胞是否仍然可见,如果没有,我不会继续。但是,这不适用于UICollectionView,而且无论如何,它也没有效率。我正在考虑迁移到使用NSOperat

我有一个
UICollectionView
,但是同样的方法应该适用于
uitableview
。我的每个单元格都包含一个从磁盘加载的映像,这是一个缓慢的操作。为了缓解这种情况,我使用了一个异步调度队列。这样做很好,但快速滚动会导致这些操作堆积起来,这样单元格将按顺序从一个图像切换到另一个图像,直到最后在最后一次调用时停止


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

您是否尝试过使用开源框架从web下载图像并异步缓存它们?它在
UITableViews
中非常有效,在
UICollectionViews
中也应该同样有效。您只需使用
UIImageView
扩展的
setImage:withURL:
方法,它确实很神奇。

我对大约400个元素的UITableView也有同样的问题,并找到了一个残酷但有效的解决方案:不要重用单元格!这取决于图像的数量和大小,但实际上iOS在释放未使用的单元格方面非常有效。。。 再次强调:这不是纯粹主义者的建议,但与异步加载相结合可以快速、无错误地启动,并且不必处理复杂的同步逻辑。

,讨论了当后台任务赶上时(从38m15开始)单元格图像的变化问题

下面是你解决这个问题的方法。将图像加载到后台队列后,使用索引路径查找显示包含该图像的项的当前单元格(如果有)。如果您得到一个单元格,请设置单元格的图像。如果得到nil,则当前没有单元格显示该项目,因此只需丢弃图像即可

- (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)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];
}
要加载图像,请从集合中获取索引路径。为此,我们必须调度回主队列,以避免集合上出现竞争条件:

- (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\u loadImageForRowAtIndexPath:
方法与上述方法相同。但当我们在单元格中实际设置图像时,我们还希望从集合中删除单元格的索引路径:

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
    [indexPathsNeedingImages removeObject: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;

    [indexPathsNeedingImages removeObject:indexPath];
}

+1用于SDWebImage。它在集合视图和表视图中的性能非常好。我的图像来源于本地。虽然SDWebImage很棒,但这并不能解决根本问题。它仍然是异步的。从缓存中快速加载图像实际上只是隐藏了问题。如果在加载图像之前重新使用单元格,则会将图像加载到错误的单元格。所以这个例子中的“魔法”是邪恶的魔法。我要去看看那个环节——不知道为什么我还没有。我会试试你的答案——谢谢你这么详细。更进一步。几乎可以工作,但倾向于多次加载同一个单元,而不加载其他单元。我认为这可能与[IndExpathsNedinGimages anyObject]每次返回相同的索引路径有关。如果你使用的是全局队列,它不是串行的。我肯定使用串行队列(dispatch_queue_create(…))我根据苹果的211会话写了一篇文章——即使人们不同意这是一个残酷但有用的解决方案……)