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.
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:
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.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.
Questions about this approach:
This question is related to both approaches:
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.
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.
To answer the other questions:
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:
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.