【问题标题】:How to cache images using URLSession in Swift如何在 Swift 中使用 URLSession 缓存图像
【发布时间】: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
    }
}

【问题讨论】:

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

标签: swift caching urlsession image-caching nscache


【解决方案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
                        }
                    }
                }
            }
        
         }
        }
        

        【讨论】:

        • 您好,欢迎来到 StackOverflow,请在回答之前正确阅读问题 - 问题中明确提到需要使用 URLSession 而不是任何第三方库来完成。
        猜你喜欢
        • 2018-12-23
        • 2023-03-28
        • 2015-12-11
        • 2018-06-11
        • 1970-01-01
        • 2016-11-30
        • 1970-01-01
        • 2018-08-05
        • 1970-01-01
        相关资源
        最近更新 更多