Swift 5, Kingfisher - 如何保证在将图像加载到 Collection 视图单元格时不重复图像?

问题描述 投票:0回答:2

我目前正在使用 Kingfisher 库加载 collectionview 单元格的缩略图图像,如果它已经记录在缓存中,它将从缓存中加载。很少会为错误的单元格加载一些图像,这会导致 collectionview 中出现重复的图像。当我尝试向下滚动然后再次关闭/出队时,图像会被正确放置。如果我也不使用从缓存中检索图像的功能,则不会出现此问题。

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SimpleThumbnailView", for: indexPath) as! ThumbnailsCollectionViewCell
        cell.thumbnailImage.kf.cancelDownloadTask()
        cell.prepareForReuse()
        if indexPath.row < thumbnails.count {
            let urlString = thumbnails[indexPath.row].thumbnail ?? ""
            ImageCache.default.retrieveImageInDiskCache(forKey: urlString) { result in
                switch result {
                case .success(let image):
                    if image == nil {
                        if let url = URL(string: urlString) {
                            DispatchQueue.main.async {
                                let resource = ImageResource(downloadURL: url, cacheKey: urlString)
                                cell.thumbnailImage.kf.setImage(with: resource, placeholder: UIImage(named: "DefaultImage"), options: [])
                            }
                        }
                    } else {
                        DispatchQueue.main.async {
                            cell.thumbnailImage.image = image
                        }
                    }
                case .failure(let error):
                    print(error)
                }
            }
            
            cell.titleLabel.text = thumbnails[indexPath.row].title
            cell.authorLabel.text = StringBuilder.authorLikes(author: thumbnails[indexPath.row].writer, likes: thumbnails[indexPath.row].likeCount)
            cell.articleId = thumbnails[indexPath.row].articleId
            cell.authorId = thumbnails[indexPath.row].writer
            if cell.articleId != -1 {
                cell.removePlaceHolder()
            }
        } else {
            thumbnailCollectionView.reloadData()
        }
        return cell
    }

这是我的 prepareForReuse()

override func prepareForReuse() {
        super.prepareForReuse()
        thumbnailImage.kf.cancelDownloadTask()
        thumbnailImage.image = UIImage(named: "DefaultImage")
    }

我还尝试在我能想到的任何地方使用 cancelDownloadTask() 函数,但这也没有用。有人知道如何解决这个问题吗?

  • 如果所有图像都从缓存磁盘加载,这个问题也不存在
ios swift uicollectionview uicollectionviewcell kingfisher
2个回答
0
投票

我不熟悉 Kingfisher,但希望它有自己的缓存。但是,您的问题是

cellForItemAt
中异步代码的经典错误。

集合(和表格)视图单元格在滚动时被重用。如果您在

cellForItemAt
中执行任何异步工作,那么在调用完成处理程序时,单元格可能已经被重新用于不同的索引路径。您的代码假设在调用完成处理程序时,为特定项目出队的
cell
仍用于该项目。根据时间和滚动项目的速度,您启动的异步代码可能不再显示在屏幕上,或者现在可能用于不同的项目。

在分配缓存或获取图像的

DispatchQueue.main.async
块中,您需要向集合视图询问与索引路径对应的单元格,而不是假设它仍然是您首先出队的单元格。如果该项目不再出现在屏幕上,这将为零。


0
投票

了解

UICollectionView
/
UITableView
中使用的重用系统很重要,这就是为什么要使单元出队。

当一个细胞消失时,它进入内存中的“可用细胞池”。
当您需要显示一个单元格时,它会检查前面提到的池中是否有可用的单元格,如果没有,它会创建一个。
那是为了优化。为什么?假设您有 10k 个单元格要显示,但屏幕上只能同时显示 5 个,让我们再添加 1 个单元格(滚动时,您只能看到第一个单元格的底部,以及新单元格的顶部) .您认为创建 10k 个单元是否有效?不,我们只创建 6 个,然后重用它们。这就是重用单元背后的全部优化(在 Android SDK 中,它是“回收”,对于回收视图,概念是相同的)。
但是当单元格离开屏幕时,它会像“这样”一样放入池中,并带有之前的所有自定义(如标签值、颜色、子视图等)。如果需要将其置于“空白”状态,您可以在那里调用

prepareForReuse()
。如果您只有一个
prepareForReuse()
并且在
UILabel
中您每次都更改它的值,则不需要覆盖
collectionView(_:cellForItemAt:)
,这是不必要的额外工作。
一旦你理解了这一点,你的代码的当前问题就是这个:
label.text = nil
您使用了两个异步调用(但一个足以产生问题),因此当调用闭包时以及当您想要设置值时,该单元格可能已经被另一个
prepareForReuse()

重用。因此,您在滚动时在错误的索引路径单元格中遇到图像的问题。

现在,您可以说来自 KingFisher/Alamofire+Image/SDWebImage 和其他库的
collectionView(_:cellForItemAt:)
也在使用“隐藏”闭包,他们没有问题。这是因为他们使用

label.text = "myValue"

,这让他们可以为对象添加额外的值(
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SimpleThumbnailView", for: indexPath) as! ThumbnailsCollectionViewCell //First Async call (I guess it's async) ImageCache.default.retrieveImageInDiskCache(forKey: urlString) { result in //Second async call DispatchQueue.main.async { cell.thumbnailImage.image = image } } }

)。那个额外的值是一个 id(图像的 URL),所以当他们想要设置图像时,他们检查 imageView 关联的 id 是否仍然是正确的,如果不正确,则该单元格已经被重用。

这是对所做工作的简化。

因此,在您的情况下,您可以为单元格添加额外的值,并检查它在闭包内是否真的相同,但实际上不需要,因为您对 KingFisher 的问题考虑过多。
取决于您如何使用 KingFisher(有额外的参数:强制刷新等),但默认情况下它会缓存自己的图像,并检查其缓存是否存在并避免网络调用。
在这里,我建议删除所有缓存检查代码(因为它已经由 lib 完成):

indexPath

现在,如果你想自己检查,你可以在库中放置断点,使用带有完成参数的

setImage(with url:)

setImage(with: urlString, placeholder: UIImage(named: "DefaultImage"), options: [], completionHandler: { 结果 切换结果{ 案例.失败(让错误): 打印(错误) 案例成功(让imageResult): //检查imageResult,可能是cacheType,source等等。 }
}

© www.soinside.com 2019 - 2024. All rights reserved.