Ios UITableView(Swift 4)中存在奇怪的缓存问题

Ios UITableView(Swift 4)中存在奇怪的缓存问题,ios,swift,Ios,Swift,我的应用程序有一个非常奇怪的问题,我所能想到的就是它是某种缓存问题。基本上,当我滚动我的UITableView时,所显示的缩略图会针对同一行多次重新加载,通常每次都会有不同的图像,然后最终会出现在正确的图像上 下面是20秒的屏幕截图: 一旦加载,应用程序应该缓存这些图像,并且图像的名称与奖金代码匹配,并且名称在JSON文件中。我不明白它为什么这样做 编辑:这是我的cellForRowAt代码: override func tableView(_ tableView: UITableView, c

我的应用程序有一个非常奇怪的问题,我所能想到的就是它是某种缓存问题。基本上,当我滚动我的UITableView时,所显示的缩略图会针对同一行多次重新加载,通常每次都会有不同的图像,然后最终会出现在正确的图像上

下面是20秒的屏幕截图:

一旦加载,应用程序应该缓存这些图像,并且图像的名称与奖金代码匹配,并且名称在JSON文件中。我不明白它为什么这样做

编辑:这是我的cellForRowAt代码:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "BonusListViewCell"
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
            fatalError("The dequeued cell is not an instance of BonusListViewCell.")
        }
        // let bonus = bonuses[indexPath.row]
        let bonus: JsonFile.JsonBonuses
        if isFiltering() {
            bonus = filteredBonuses[indexPath.row]
        } else {
            bonus = bonuses[indexPath.row]
        }

        let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
        let url = URL(string: urlString)
        cell.primaryImage.downloadedFrom(url: url!)
        cell.nameLabel.text = bonus.name.capitalized
        cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
        cell.categoryLabel.text = bonus.category
        cell.valueLabel.text = "\(bonus.value)"
        cell.cityLabel.text = "\(bonus.city.capitalized),"
        cell.stateLabel.text = bonus.state.localizedUppercase

        return cell
    }
以下是我的下载自功能:

extension UIImageView {
    func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
        contentMode = mode
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let image = UIImage(data: data)
                else { return }
            DispatchQueue.main.async() {
                self.image = image
            }
            }.resume()
    }
    func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
        guard let url = URL(string: link) else { return }
        downloadedFrom(url: url, contentMode: mode)
    }
}
我的UITableViewCell代码是:

import UIKit

class BonusListViewCell: UITableViewCell {

    //MARK: Properties
    @IBOutlet weak var bonusCodeLabel: UILabel!
    @IBOutlet weak var categoryLabel: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!
    @IBOutlet weak var cityLabel: UILabel!
    @IBOutlet weak var stateLabel: UILabel!
    @IBOutlet weak var flavorText: UITextView!
    @IBOutlet weak var primaryImage: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

您的问题是,在重新使用单元格后,以前调度的图像获取将完成,从而导致过时的图像依次出现,直到加载最终的正确图像

现有的一些框架可以让你更轻松地完成这项工作,还可以执行缓存,但你可能不想在你的应用程序中引入第三方代码

一种方法是使用UIImageView子类而不是扩展,以便可以为当前url和内存缓存添加属性:

class LoadableImageView: UIImageView {

    private var currentURL: URL?
    private static imageCache = NSCache<NSURL,UIImage>()
    private static cacheUpdateQueue = DispatchQueue.global(qos: .userInitiated)

    func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
        contentMode = mode
        self.currentURL = url

        if let image = LoadableImageView.imageCache.object(forKey:url as NSURL) {
            self.image = image
            return
        }

        URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let image = UIImage(data: data),
                url == self.currentURL
                else { return }
                LoadableImageView.cacheUpdateQueue.sync {
                    LoadableImageView.imageCache.setObject(image, forKey: url as NSURL)
                }
                DispatchQueue.main.async() {
                     self.image = image 
                }
            }.resume()
    }
    func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
        guard let url = URL(string: link) else { return }
        downloadedFrom(url: url, contentMode: mode)
    }
}
您需要在prepareForReuse或cellForRowAt中设置适当的占位符图像或nil

在你的牢房里


但是最好使用像Alamofire或SDWebImage这样的库

使用SDWebImage或AlamofireImage它会注意到这一点请编辑问题以显示cellForRowAt方法以及加载缩略图所用的代码(如果不在该方法中)。@Paulw11我已添加了所需的代码。缩略图提取也在那里。你能在中显示代码吗。downloadedFromurl:url!-这就是图像下载的实际位置occurs@Paulw11我已经添加了它。你是正确的,我宁愿避免第三方代码,如果可能的话。在搜索我的项目时,我在项目中的任何地方都没有PrepareForuse。我认为它应该包含在我的UITableViewCell代码中,我已经将其添加到我的原始问题中。如何使用prepareForReuse指向我的DefaultImage?没错,prepareForReuse可以在单元格子类中声明。您可以将self.imageView.image=nil或self.imageView.image=UIImagenamed:placeholder.jpg或其他任何内容随意告诉我我找错了树,但我想知道是否应该使用CoreData来存储这些图像。所讨论的图像是样本,用户将使用该应用程序获取自己的版本。我的想法是,由于它们是按州划分的,大多数车手不会查看所有350张图片,所以我可以根据需要加载它们并保留它们。在第一次加载时将它们保存到CoreData,而不是一直尝试缓存或获取,这有意义吗?如果它们本质上是静态的,您可以这样做。您可以使用核心数据,也可以将文件保存在documents目录中
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cellIdentifier = "BonusListViewCell"
    guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
        fatalError("The dequeued cell is not an instance of BonusListViewCell.")
    }
    // let bonus = bonuses[indexPath.row]
    let bonus: JsonFile.JsonBonuses
    if isFiltering() {
        bonus = filteredBonuses[indexPath.row]
    } else {
        bonus = bonuses[indexPath.row]
    }

    let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
    let url = URL(string: urlString)

    //set image url
    cell.imageUrl = URL(string: images[indexPath.row])

    cell.nameLabel.text = bonus.name.capitalized
    cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
    cell.categoryLabel.text = bonus.category
    cell.valueLabel.text = "\(bonus.value)"
    cell.cityLabel.text = "\(bonus.city.capitalized),"
    cell.stateLabel.text = bonus.state.localizedUppercase

    return cell
}
import UIKit

class BonusListViewCell: UITableViewCell {

    //MARK: Properties
    @IBOutlet weak var bonusCodeLabel: UILabel!
    @IBOutlet weak var categoryLabel: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!
    @IBOutlet weak var cityLabel: UILabel!
    @IBOutlet weak var stateLabel: UILabel!
    @IBOutlet weak var flavorText: UITextView!
    @IBOutlet weak var primaryImage: UIImageView!
    @IBOutlet weak var activity: UIActivityIndicatorView!

    var imageUrl: URL? {
        didSet {
            loadImage()
        }
    }
    var task: URLSessionDataTask?

    override func awakeFromNib() {
        super.awakeFromNib()
        clean()
    }

    func loadImage() {

        //you need cancel previous task
        task?.cancel()

        clean()
        guard let imageUrl = imageUrl else {
            return
        }
        activity.startAnimating()
        task = URLSession.shared.dataTask(with: imageUrl) { [weak self] data, response, error in
            DispatchQueue.main.async {
                self?.activity.stopAnimating()
                if error == nil, let data = data {
                    self?.primaryImage.image = UIImage(data: data)
                }
            }
        }
        task?.resume()
    }

    func clean() {
        primaryImage.image = nil
    }
}