ios Swift 5,Kingfisher -如何保证在将图像加载到集合视图单元格时没有重复的图像?

zphenhs4  于 2023-05-19  发布在  iOS
关注(0)|答案(2)|浏览(230)

我目前正在使用翠鸟库加载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()函数,但这也不起作用。有人能想出解决这个问题的办法吗?

  • 如果从该高速缓存磁盘加载所有映像,也不会出现此问题
jtw3ybtb

jtw3ybtb1#

理解UICollectionView/UITableView中使用的重用系统非常重要,这就是为什么要将单元出列。
当一个单元格消失时,它会进入内存中的“可用单元格池”。
当你需要显示一个单元格时,它会检查前面提到的池中是否有可用的单元格,如果没有,它会创建一个。
这是为了优化。为什么呢?让我们假设你有10 k个单元格要显示,但是在屏幕上只能同时显示5个,让我们再添加一个单元格(当滚动时,你只能看到第一个单元格的底部,而新单元格的顶部)。你认为创造一万个细胞会有效率吗?不,让我们只创建6个,并重复使用它们。这就是重用单元格背后的整个优化(在Android SDK中,它是“回收”,对于回收视图,概念是相同的)。
但是当单元格从屏幕中消失时,它会以“such”的形式放入池中,并带有之前所有的自定义(如标签值,颜色,子视图等)。在那里,它将调用prepareForReuse()将其置于“空白”状态(不要自己调用它)。例如,如果您只有一个UILabel,并且在collectionView(_:cellForItemAt:)中每次都更改其值,则不需要覆盖prepareForReuse()(在prepareForReuse()中是label.text = nil,在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
        }
    }
}

您使用了两个异步调用(但一个就足以产生问题),因此当调用闭包时,当您想要设置值时,单元格可能已经被另一个indexPath重用。因此,你得到的问题有错误的索引路径单元格的图像滚动时.
现在,你可以说来自KingFisher/Alamofire+Image/SDWebImage的setImage(with url:)和其他库也使用了“隐藏”闭包,它们没有这个问题。这是因为它们使用associated_object,这允许它们向对象添加额外的值(UIImageView)。这个额外的值是一个id(图像的URL),所以当他们想要设置图像时,他们会检查imageView关联的id是否仍然是正确的,如果不是,则单元格已经被重用。
这是对所做事情的简化。
所以在你的例子中,你可以向单元格中添加额外的值,并检查闭包中的值是否真的相同,但实际上这是不需要的,因为你对KingFisher的问题想得太多了。
根据您使用KingFisher的方式(有额外的参数:强制刷新等),但默认情况下,它会缓存自己的图像,并检查其缓存是否存在,并避免Web调用。
在这里,我建议删除所有该高速缓存检查代码(因为它已经由lib完成了):

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SimpleThumbnailView", for: indexPath) as! ThumbnailsCollectionViewCell
    if indexPath.row < thumbnails.count {
        let urlString = thumbnails[indexPath.row].thumbnail ?? ""
        cell.thumbnailImage.kf.setImage(with: urlString, placeholder: UIImage(named: "DefaultImage"), options: []) //Might need a if let url here
        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()
        }
    return cell
}

现在,如果你想自己检查,你可以把断点放在库中,使用带有完成参数的setImage()。我没有测试它,但我猜答案是从哪里来的图像,缓存或没有可能是在RetrieveImageResult值。

setImage(with: urlString, placeholder: UIImage(named: "DefaultImage"), options: [], completionHandler: { result in 
    switch result {
        case .failure(let error):
            print(error)
        case success(let imageResult):
            //check imageResult, maybe cacheType, source, etc.
    }
}

你遇到的另一个问题是重新加载整个collectionView。

ssm49v7z

ssm49v7z2#

我不熟悉翠鸟,但希望它做自己的缓存。但是,您的问题是cellForItemAt中异步代码的一个典型bug。
滚动时会重用集合(和表)视图单元格。如果在cellForItemAt中执行任何异步工作,那么在调用完成处理程序时,单元格可能已经被另一个索引路径重用。您的代码假设为特定项出列的cell在调用完成处理程序时仍用于该项。根据时间和滚动项目的速度,启动的异步代码可能不再显示在屏幕上,或者现在用于其他项目。
DispatchQueue.main.async块中,您需要为索引路径对应的单元格指定ask the collection view,而不是假设它仍然是您首先出队的单元格。如果该项目不再显示在屏幕上,则此值为空。

相关问题