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

Posted

技术标签:

【中文标题】如何在 Swift 中使用 URLSession 缓存图像【英文标题】:How to cache images using URLSession in Swift 【发布时间】:2017-04-13 21:50:01 【问题描述】:

我想增强下面的代码以缓存图像,并且仅在之前未缓存图像时才下载它们。我似乎找不到任何关于如何使用 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
    

【问题讨论】:

nshipster.com/nsurlcache 不相关,你的if let strongSelf = self strongSelf.image = image 可以简化为self?.image = image FWIW, NSURLCache 将根据服务器响应标头中提供的内容进行缓存。此外,它将根据记录不充分的规则限制缓存,最值得注意的是,如果下载量超过总缓存的 5%,它不会缓存它,无论服务器的标头说什么(这是增加缓存大小的原因之一如 Leo 提供的链接中所述)。 【参考方案1】:

为 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

【讨论】:

【参考方案2】:

对此的一个潜在解决方案是利用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

【讨论】:

【参考方案3】:

URLSession DataTask 默认会自动缓存图片,只要服务端的缓存设置正常,你不需要在客户端做任何事情。图片是静态资源,不会在短时间内发生变化,因此服务器通常会将“Cache-Control”设置为“public, max-age:xxxxx”。 URLSession 默认缓存策略将图像缓存在内存和磁盘中。但是,它不会缓存大小超过为 URLCache 分配的磁盘大小的 5% 的图像,并且它也不会在后台线程中进行缓存。

【讨论】:

【参考方案4】:
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
                
            
        
    

 

【讨论】:

您好,欢迎来到 ***,请在回答之前正确阅读问题 - 问题中明确提到需要使用 URLSession 而不是任何第三方库来完成。

以上是关于如何在 Swift 中使用 URLSession 缓存图像的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 URLSession 解析并将它们导入 Swift 的数组中?

Swift:如何在异步 urlsession 函数中返回一个值?

Swift:如何在异步 urlsession 函数中返回一个值?

如何在从 Swift 3 中的函数返回结果之前等待 URLSession 完成

如何使用 Swift URLSession 通过 REST api 将图像和其他参数作为表单数据发布?

从 URLSession 完成处理函数 Swift 3 返回字符串