Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cache images using URLSession in Swift

I would like to enhance the code below to cache images and only download them if they haven't been cached previously. I can't seem to find any good examples of how to use URLSession object to do this.

extension UIImageView {
    func loadImageWithURL(_ url: URL) -> URLSessionDownloadTask {
        let session = URLSession.shared

        let downloadTask = session.downloadTask(with: url, completionHandler: { [weak self] url, response, error in

            if error == nil, let url = url,
                let data = try? Data(contentsOf: url), let image = UIImage(data: data) {

                    DispatchQueue.main.async {
                        if let strongSelf = self {
                            strongSelf.image = image
                        }
                    }
            }
        })
        downloadTask.resume()
        return downloadTask
    }
}
like image 586
Martin Muldoon Avatar asked Nov 29 '16 18:11

Martin Muldoon


People also ask

What is image caching in Swift?

Cache is a hardware or software component that stores data so future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation, or the duplicate of data stored elsewhere.

Does Async image cache?

The Swift standard library provides the AsyncImage component providing a caching implementation using URLCache.

What is Nscache?

A mutable collection you use to temporarily store transient key-value pairs that are subject to eviction when resources are low.

Does flutter cache images?

The goodness of caching network images in a Flutter application does more than just boot up the performance of the application itself. Other significant benefits are: Reduce the burden on your server and reduce the cost you have to pay for bandwidth in loading images.

How to work with remote files and images in Swift?

Using URLSession and FileManager you can work with remote files and images in Swift without any external dependencies. Why not use SDWebImage and Cocoapods to download and cache files and images?

How to use caching in Swift?

Caching in Swift 1 Part of the system. Caching is one of those tasks that at first might seem much simpler than it actually is. ... 2 It all starts with a declaration. The first thing we’ll do is to declare our new cache type. ... 3 Avoiding stale data. ... 4 Persistent caching. ... 5 Conclusion. ...

How do I cache a downloaded file or image?

A file path is needed to be able to cache a downloaded file or image. The function download (url:, toFile:, completion:) builds off of URLSession.downloadTask (with:), taking in a toFile file path and handles the download response callback to cache downloaded data.

How do I download a file from urlsession?

The function download (url:, toFile:, completion:) builds off of URLSession.downloadTask (with:), taking in a toFile file path and handles the download response callback to cache downloaded data. The first parameter of the downloadTask (with:) callback is a temporary URL tempURL to the downloaded file.


3 Answers

One potential solution to this would be to utilize NSCache to take care of caching. Essentially what you would do is check if you already have the image locally to load from rather than fetching every time before you actually make a request.

Here's one of my implementations, however - it's a subclass rather than an extension:

class CustomImageView: UIImageView {

    // MARK: - Constants

    let imageCache = NSCache<NSString, AnyObject>()

    // MARK: - Properties

    var imageURLString: String?

    func downloadImageFrom(urlString: String, imageMode: UIViewContentMode) {
        guard let url = URL(string: urlString) else { return }
        downloadImageFrom(url: url, imageMode: imageMode)
    }

    func downloadImageFrom(url: URL, imageMode: UIViewContentMode) {
        contentMode = imageMode
        if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) as? UIImage {
            self.image = cachedImage
        } else {
            URLSession.shared.dataTask(with: url) { data, response, error in
                guard let data = data, error == nil else { return }
                DispatchQueue.main.async {
                    let imageToCache = UIImage(data: data)
                    self.imageCache.setObject(imageToCache!, forKey: url.absoluteString as NSString)
                    self.image = imageToCache
                }
            }.resume()
        }
    }
}

Additionally, here's a useful resource: https://www.hackingwithswift.com/example-code/system/how-to-cache-data-using-nscache

like image 166
Brandon Lee Avatar answered Oct 20 '22 08:10

Brandon Lee


URLSession DataTask by default will cache the image automatically and you don't need to do anything on client-side as long as the cache setting on the server is normal. Images are static assets and won't change in short time, as the result, server will normally set "Cache-Control" to "public, max-age:xxxxx". URLSession default cache policy will cache the image both in memory and disk. However, it won't cache the image whose size is larger than 5% of disk size allocated for URLCache, and it doesn't do caching in background thread either.

like image 42
James Wang Avatar answered Oct 20 '22 06:10

James Wang


Updated for Swift 4

import UIKit

let imageCache = NSCache<AnyObject, AnyObject>()

class ImageLoader: UIImageView {

    var imageURL: URL?

    let activityIndicator = UIActivityIndicatorView()

    func loadImageWithUrl(_ url: URL) {

        // setup activityIndicator...
        activityIndicator.color = .darkGray

        addSubview(activityIndicator)
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true

        imageURL = url

        image = nil
        activityIndicator.startAnimating()

        // retrieves image if already available in cache
        if let imageFromCache = imageCache.object(forKey: url as AnyObject) as? UIImage {

            self.image = imageFromCache
            activityIndicator.stopAnimating()
            return
        }

        // image does not available in cache.. so retrieving it from url...
        URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in

            if error != nil {
                print(error as Any)
                DispatchQueue.main.async(execute: {
                    self.activityIndicator.stopAnimating()
                })
                return
            }

            DispatchQueue.main.async(execute: {

                if let unwrappedData = data, let imageToCache = UIImage(data: unwrappedData) {

                    if self.imageURL == url {
                        self.image = imageToCache
                    }

                    imageCache.setObject(imageToCache, forKey: url as AnyObject)
                }
                self.activityIndicator.stopAnimating()
            })
        }).resume()
    }
}

Usage:

// assign ImageLoader class to your imageView class
let yourImageView: ImageLoader = {

    let iv = ImageLoader()
    iv.frame = CGRect(x: 10, y: 100, width: 300, height: 300)
    iv.backgroundColor = UIColor(red: 0.94, green: 0.94, blue: 0.96, alpha: 1.0)
    iv.contentMode = .scaleAspectFill
    iv.clipsToBounds = true
    return iv
}()


// unwrapped url safely...
   if let strUrl = "https://picsum.photos/300/300".addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
      let imgUrl = URL(string: strUrl) {

      yourImageView.loadImageWithUrl(imgUrl) // call this line for getting image to yourImageView
}
like image 37
iAj Avatar answered Oct 20 '22 07:10

iAj