在UITableViewCell中下载图像:数据与URLSession?
我有一个UITableView
,我需要每个单元格从提供的URL下载图像并显示它。 这看起来是一个非常常见的场景,事实上我发现了几个与此相关的post和问题,但我还不清楚应该采用哪种最佳方法。
第一个,我有一个使用Data(contentsOf:)
。 这是我在UITableViewCell
的代码:
class MyCell: UITableViewCell { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var imageView: UIImageView! var model: Model? { willSet { activityIndicator.startAnimating() configureImage(showImage: false, showActivity: true) } didSet { guard let imageSmallUrlStr = model?.imageSmallUrl, let imageUrl = URL(string: imageSmallUrlStr) else { activityIndicator.stopAnimating() configureImage(showImage: false, showActivity: false) return } DispatchQueue.global(qos: .userInitiated).async { var image: UIImage? let imageData = try? Data(contentsOf: imageUrl) if let imageData = imageData { image = UIImage(data: imageData) } DispatchQueue.main.async { self.imageView.image = image self.configureImage(showImage: true, showActivity: false) } } } override func awakeFromNib() { super.awakeFromNib() configureImage(showImage: false, showActivity: false) } override func prepareForReuse() { super.prepareForReuse() model = nil } // Other methods }
这种方法看起来非常简单快速,至少在我测试它在模拟器上运行时。 虽然,我有一些问题:
- 是否真的适合使用
Data(contentsOf:)
从HTTP
URL下载图像? 它可以工作,但它可能更适合获取你拥有的文件或其他类型的东西,它不是网络的最佳解决方案。 - 我想在全局异步队列中执行该操作是正确的,对吧?
- 如果更新图像URL并且需要下载不同的图像URL,是否可以使用此方法取消图像下载? 另外,当一个单元格从表中出列时,我应该取消下载,还是自动完成?
- 此外,我不确定这一点:由于几个单元格将同时显示在表格中,因此这些单元格需要同时下载并显示其图像。 并且还可能发生这样的情况:当图像完成下载时,相应的单元格已经从表中出列,因为用户已经滚动。 这种方法是否确保多个单元同时下载图像,并且每个下载的图像都正确设置为相应的单元格?
现在,这是我的另一种方法,即使用URLSession
:
class MyCell: UITableViewCell { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var imageView: UIImageView! var imageTask: URLSessionDownloadTask? var model: Model? { willSet { activityIndicator.startAnimating() configureImage(showImage: false, showActivity: true) } didSet { imageTask?.cancel() guard let imageSmallUrlStr = model?.imageSmallUrl, let imageUrl = URL(string: imageSmallUrlStr) else { activityIndicator.stopAnimating() configureImage(showImage: false, showActivity: false) return } imageTask = NetworkManager.sharedInstance.getImageInBackground(imageUrl: imageUrl, completion: { [weak self] (image, error) in DispatchQueue.main.async { guard error == nil else { self?.activityIndicator.stopAnimating() self?.configureImage(showImage: false, showActivity: false) return } self?.imageView.image = image self?.activityIndicator.stopAnimating() self?.configureImage(showImage: true, showActivity: false) } }) } } // Other methods }
通过这种方法,我可以手动取消任务,但是当我在模拟器上运行应用程序时,它看起来更慢。 事实上,在尝试下载许多图像时,我收到了一些“已取消”的错误。 但这样我就可以处理后台下载。
关于这种方法的问题:
- 我怎样才能确保下载的图像显示在相应的单元格中? 与前面描述的问题相同:此时单元格可能已从表中出列。
这个问题与两种方法有关:
- 我不想将图像保存到文件中,但如果再次显示单元格我不想再次下载并且我已经这样做了。 什么应该是缓存它们的最佳方法?
您永远不应该使用NSData的contentsOfURL方法来检索非本地数据,原因有两个:
- 文档明确说明这样做是不受支持的。
- 名称解析和检索同步发生,阻塞当前线程。
在你的情况下,在并发队列上执行它,最后一个并不像它本来那样糟糕,但是如果你获取足够的资源,我怀疑它会导致相当大的开销。
您的NSURLSession方法显得较慢的一个原因是我认为您可能正在使用其他方法同时获取所有资源(非常难以点击服务器)。 更改NSURLSession中的并发限制可能会稍微改善其性能,但只有在您知道服务器可以处理它时才会这样做。
回答其他问题:
-
您可以通过多种方式将图像绑定到特定单元格,但最常见的方法是使用管理所有请求的单例,并在该单例中:
- 保留一个将数据任务映射到唯一标识符的字典。 将其设置为单元的可访问性标识符,并使用内置查找方法查找具有该可访问性标识符的单元。 如果它不再存在,则丢弃响应。
- 保留一个将数据任务映射到块的字典。 请求完成后,运行该块。 让块保持对单元格的强引用。
在任何一种情况下,都要将URL存储在单元格的属性中,并确保它在设置图像之前与请求的原始URL匹配,这样如果单元格被重用,则不会使用错误的图像覆盖其现有图像。陈旧的要求。
- NSURLCache是你的朋友。 创建一个合理大小的磁盘缓存,它应该注意避免不必要的网络请求(假设您的服务器支持正确的缓存)。