在UITableViewCell中下载图像:数据还是URLSession? [英] Downloading images in UITableViewCell: Data vs URLSession?

查看:67
本文介绍了在UITableViewCell中下载图像:数据还是URLSession?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个UITableView,我需要它的每个单元格才能从提供的URL下载图像并显示它.这似乎是一个很常见的情况,确实我发现了一些与此相关的帖子和​​问题,但我仍不清楚应该采用哪种最佳方法.

I have a UITableView and I need each of its cells to download an image from a provided URL and show it. This looks to be a quite common scenario and indeed I found several posts and questions related to this, but I am not still clear about which the best approach should be.

第一个,我有一个使用Data(contentsOf:).这是我的UITableViewCell中的代码:

First one, I have one using Data(contentsOf:). This is the code I have in my 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
}

至少在我在模拟器上测试它时,这种方法看起来非常简单快捷.不过,我对此有一些疑问:

This approach looks quite straightforward and fast, at least when I test it running on a simulator. Though, I have some questions regarding it:

  1. 使用Data(contentsOf:)HTTP URL下载图像实际上是否合适?它可以工作,但是也许它更适合获取您拥有的文件或其他东西,并且它并不是最佳的网络解决方案.
  2. 我想在全局异步队列中执行该操作是正确的事情,对吧?
  3. 如果图像URL已更新并且我需要下载其他图像,则这种方法是否可以取消该图像下载?另外,当单元格从表中出队时,我应该取消下载吗?还是自动完成此操作?
  4. 此外,我不确定:由于表中将同时显示几个单元格,因此这些单元格需要同时下载并显示其图像.而且还可能发生的情况是,当图像下载完成时,由于用户已滚动,因此相应的单元格已从表格中出队.这种方法是否可以确保多个单元同时下载图像,并且每个下载的图像都已正确设置为其对应的单元?
  1. Is it actually appropriate to download an image from an HTTP URL by using Data(contentsOf:)? It works, but maybe it is more suitable for getting files you have or another kind of stuff, and it is not the best solution for networking.
  2. I guess that performing that performing that in a global async queue is the correct thing to do, right?
  3. Is it possible with this approach to cancel that image download if the image URL is updated and I need to download a different one? Also, should I cancel the download when a cell is dequeued from the table, or is this automatically done?
  4. Also, I'm not sure about this: since several cells are going to be shown in the table at same time, those cells need to download and show their image concurrently. And could also happen that, when an image has finished being downloaded, the corresponding cell has been dequeued from the table because the user has scrolled. Does this approach ensure that several cells are concurrently downloading the image, and that each downloaded image is correctly set to its corresponding cell?

现在,这是我使用的另一种方法,即使用URLSession的方法:

Now, this is the other approach I have, the one using 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

}

使用这种方法,我可以手动取消任务,但是当我在模拟器上运行该应用程序时,它看起来会比较慢.实际上,当尝试下载许多图像时,出现了一些已取消"错误.但是通过这种方式,我可以处理后台下载.

With this approach, I can manually cancel the task, but when I run the app on simulator it looks like it is slower. In fact, I am getting some "cancelled" errors when trying to download many of the images. But this way I could be able to handle background downloads.

有关此方法的问题:

  1. 如何确保下载的图像显示在其相应的单元格中?与前面所述的问题相同:此时该单元格可能已从表中出队.

此问题与两种方法都有关:

This question is related to both approaches:

  1. 我不想将图像保存到文件中,但是如果再次显示该单元格并且我已经这样做了,我就不想再次下载.缓存它们的最佳方法是什么?

推荐答案

出于两个原因,您永远不要使用NSData的contentsOfURL方法检索非本地数据:

You should never use NSData's contentsOfURL method to retrieve non-local data, for two reasons:

  • 文档明确指出不支持这样做.
  • 名称解析和检索同步进行,从而阻止了当前线程.

在您的情况下,在并发队列上执行此操作,最后一个并没有那么糟糕,但是,如果您获取足够的资源,我怀疑这会导致相当大的开销.

In your case, doing it on a concurrent queue, the last one isn't quite as bad as it otherwise would be, but if you're fetching enough resources, I suspect it would result in a fair amount of overhead.

您的NSURLSession方法出现速度较慢的一个原因是,我认为,您可能正在与其他方法同时获取所有资源(确实很难打服服务器).更改NSURLSession中的并发限制可能会稍微提高其性能,但是只有在您知道服务器可以处理的情况下才能这样做.

One reason why your NSURLSession approach appears slower is that I think you're probably fetching all the resources simultaneously (hitting the server really hard) with your other approach. Changing the concurrency limit in NSURLSession might improve its performance a bit, but only do that if you know the server can handle it.

要回答其他问题:

  • 您可以通过多种方式将图像绑定到特定的单元,但是最常见的方法是让一个单例来管理所有请求,并且在该单例中,可以:

  • You can tie the image to a particular cell in any number of ways, but the most common approach is to have a singleton that manages all the requests, and in that singleton, either:

  • 保留将数据任务映射到唯一标识符的字典.将其设置为单元的可访问性标识符,并使用内置的查找方法查找具有该可访问性标识符的单元.如果不再存在,则丢弃响应.
  • 保留将数据任务映射到块的字典.请求完成后,运行该块.让该块对单元格保持强烈的引用.

无论哪种情况,都将URL存储在单元格中的一个属性中,并在设置图像之前确保它与请求的原始URL匹配,这样,如果单元格被重用,则不会用错误的名称覆盖其现有图像.图片来自过时的请求.

In either case, store the URL in a property on the cell, and make sure it matches the request's original URL before setting the image, so that if the cell got reused, you won't overwrite its existing image with the wrong image from a stale request.

这篇关于在UITableViewCell中下载图像:数据还是URLSession?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆