如何在Swift中使用URLSession缓存图像

13

我希望优化下面的代码,以便缓存图像,并且只有在它们之前没有被缓存时才下载它们。我找不到如何使用URLSession对象来实现这一点的好例子。

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
    }
}

1
http://nshipster.com/nsurlcache/ - Leo Dabus
1
无关的是,你的 if let strongSelf = self { strongSelf.image = image } 可以简化为 self?.image = image - Rob
1
值得一提的是,NSURLCache会根据服务器响应头中提供的内容进行缓存。此外,它还会根据文档不清晰的规则来限制缓存,特别是如果下载超过总缓存的5%,它将不会对其进行缓存,无论服务器头信息如何(这是Leo提供的链接所概述的增加缓存大小的原因之一)。 - Rob
4个回答

23

已更新为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()
    }
}

用法:

// 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
}

9

一个可能的解决方案是利用NSCache来处理缓存。基本上,您需要做的是在实际发出请求之前检查是否已经有本地图像可供加载,而不是每次都获取。

这是我的一个实现,但是它是一个子类而不是扩展:

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()
        }
    }
}

此外,这里有一个有用的资源:https://www.hackingwithswift.com/example-code/system/how-to-cache-data-using-nscache


4

默认情况下,URLSession DataTask 会自动缓存图像,只要服务器上的缓存设置正常,客户端无需做任何处理。图像是静态资产,不会在短时间内更改,因此服务器通常将 "Cache-Control" 设置为 "public, max-age:xxxxx"。URLSession 默认缓存策略将在内存和硬盘中缓存图像。但是,它不会缓存大小大于 URLCache 分配的磁盘容量的5%的图像,并且也不会在后台线程中进行缓存。


1
这应该是被接受的答案。让CFNetwork经过实战检验的缓存代码为您完成工作。 - John Scalo
如果这篇帖子是真的,那么是的,对于小图像确实不需要其他缓存管理。 - Nicolas Manzini

-1
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {

    func loadImageFromUrl(urlString: String)  {
        if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage{
            self.image = imageFromCache
            return
        }

        Alamofire.request(urlString, method: .get).response { (responseData) in
            if let data = responseData.data {
               DispatchQueue.main.async {
                if let imageToCache = UIImage(data: data){
                    imageCache.setObject(imageToCache, forKey: urlString as AnyObject)
                    self.image = imageToCache
                }
            }
        }
    }

 }
}

3
你好,欢迎来到StackOverflow,请在回答问题之前仔细阅读问题 - 问题中明确提到需要使用URLSession而不是任何第三方库。 - Abhirajsinh Thakore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接