• 作为一个iOS开发你也许不知道图片的内存管理机制、也不不知道图片的解析机制、但是你肯定用过SDWebImage,也许用过Kingfisher。
  • 大多数人习惯了只要加载图片都用第三方, 但是你真正了解第三方为你做了什么吗?为什么我们不自己去下载图片,并且管理呢?
    也许你看完这篇文章心里就会有个答案

我们就从源码开始说起吧:

  • 首先,我们就一起分析一下该框架的组成。
    将KF导入工程后,下面是其结构:

    项目结构

    除去support Files, 项目大致分为5个模块:

  • 图片存储模块(imageCache)
  • 图片下载模块(imageDownloader)
  • imageView分类模块(imageView+Kingfisher)
  • 图片加载模块(kingfisherManager)
  • 缓存key处理模块(String+MD5)
    其核心文件是KingfisherManager里面包含了图片赋值策略。
    我们从给图片赋值开始看:
 // kf是命名空间,swift中方法名不在是前缀加下划线显示而是采用了命名空间形式,跟原生库更接近
let imageV = UIImageView()
imageV.kf.setImage(with: URL(string: self.imageStr), placeholder: nil, options: nil, progressBlock: nil, completionHandler: nil)

我们创建了一个imageView,然后通过kf调用setImage给imageView赋值,我们点进去看看做了什么

// discardableResult表示返回值可以忽略不会出现警告@discardableResultpublic func setImage(with resource: Resource?,placeholder: Placeholder? = nil,options: KingfisherOptionsInfo? = nil,progressBlock: DownloadProgressBlock? = nil,completionHandler: CompletionHandler? = nil) -> RetrieveImageTask{guard let resource = resource else {self.placeholder = placeholdersetWebURL(nil)completionHandler?(nil, nil, .none, nil)return .empty}// options是个数组,存储了关于图片加载的配置var options = KingfisherManager.shared.defaultOptions + (options ?? KingfisherEmptyOptionsInfo)let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nilif !options.keepCurrentImageWhileLoading || noImageOrPlaceholderSet { // Always set placeholder while there is no image/placehoer yet.self.placeholder = placeholder}// 开启加载动画let maybeIndicator = indicatormaybeIndicator?.startAnimatingView()setWebURL(resource.downloadURL)if base.shouldPreloadAllAnimation() {options.append(.preloadAllAnimationData)}// 真正加载图片的方法let task = KingfisherManager.shared.retrieveImage(with: resource,options: options,progressBlock: { receivedSize, totalSize inguard resource.downloadURL == self.webURL else {return}if let progressBlock = progressBlock {progressBlock(receivedSize, totalSize)}},completionHandler: {[weak base] image, error, cacheType, imageURL inDispatchQueue.main.safeAsync {maybeIndicator?.stopAnimatingView()guard let strongBase = base, imageURL == self.webURL else {completionHandler?(image, error, cacheType, imageURL)return}self.setImageTask(nil)guard let image = image else {completionHandler?(nil, error, cacheType, imageURL)return}guard let transitionItem = options.lastMatchIgnoringAssociatedValue(.transition(.none)),case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else{self.placeholder = nilstrongBase.image = imagecompletionHandler?(image, error, cacheType, imageURL)return}#if !os(macOS)UIView.transition(with: strongBase, duration: 0.0, options: [],animations: { maybeIndicator?.stopAnimatingView() },completion: { _ inself.placeholder = nilUIView.transition(with: strongBase, duration: transition.duration,options: [transition.animationOptions, .allowUserInteraction],animations: {// Set image property in the animation.transition.animations?(strongBase, image)},completion: { finished intransition.completion?(finished)completionHandler?(image, error, cacheType, imageURL)})})#endif}})setImageTask(task)return task}
  • 这个就是图片的设置方法,大概的流程注释已经写清楚了, 还有三点需要详细说明下:

    • options:是KingfisherOptionsInfoItem类型的枚举,存储了关于图片缓存策略相关的以及下载策略相关配置
    • retrieveImage是真实获取图片数据方法
    • completionHandler: 是查找或下载结果的回调。
@discardableResultpublic func retrieveImage(with resource: Resource,options: KingfisherOptionsInfo?,progressBlock: DownloadProgressBlock?,completionHandler: CompletionHandler?) -> RetrieveImageTask

我们可以看到这个方法内部根据option参数不同调用了不同的方法:

  • 第一个方法:
func downloadAndCacheImage(with url: URL,forKey key: String,retrieveImageTask: RetrieveImageTask,progressBlock: DownloadProgressBlock?,completionHandler: CompletionHandler?,options: KingfisherOptionsInfo) -> RetrieveImageDownloadTask?
  • 第二个方法:
func tryToRetrieveImageFromCache(forKey key: String,with url: URL,retrieveImageTask: RetrieveImageTask,progressBlock: DownloadProgressBlock?,completionHandler: CompletionHandler?,options: KingfisherOptionsInfo)

至此图片加载的简单流程结束了。

一 、接下来我们看第一个图片加载策略:

  @discardableResultfunc downloadAndCacheImage(with url: URL,forKey key: String,retrieveImageTask: RetrieveImageTask,progressBlock: DownloadProgressBlock?,completionHandler: CompletionHandler?,options: KingfisherOptionsInfo) -> RetrieveImageDownloadTask?{// 图片下载器, 内部定义了默认超时时间是15s,图片下载所需的URLSession, 以及其它一些配置let downloader = options.downloader ?? self.downloader// 单独的数据处理队列let processQueue = self.processQueue// 下载图片,并通过回调函数返回return downloader.downloadImage(with: url, retrieveImageTask: retrieveImageTask, options: options,progressBlock: { receivedSize, totalSize in// 下载进度相关progressBlock?(receivedSize, totalSize)},// 下载结果回调completionHandler: { image, error, imageURL, originalData inlet targetCache = options.targetCache ?? self.cache// 如果图片下载失败则从缓存查找,并返回if let error = error, error.code == KingfisherError.notModified.rawValue {// Not modified. Try to find the image from cache.// (The image should be in cache. It should be guaranteed by the framework users.)targetCache.retrieveImage(forKey: key, options: options, completionHandler: { (cacheImage, cacheType) -> Void incompletionHandler?(cacheImage, nil, cacheType, url)})return}// 存储图片数据if let image = image, let originalData = originalData {targetCache.store(image,original: originalData,forKey: key,// 根据option存储策略存储原始图片                                   processorIdentifier:options.processor.identifier,cacheSerializer: options.cacheSerializer,toDisk: !options.cacheMemoryOnly,completionHandler: {guard options.waitForCache else { return }let cacheType = targetCache.imageCachedType(forKey: key, processorIdentifier: options.processor.identifier)completionHandler?(image, nil, cacheType, url)})if options.cacheOriginalImage && options.processor != DefaultImageProcessor.default {let originalCache = options.originalCache ?? targetCachelet defaultProcessor = DefaultImageProcessor.defaultprocessQueue.async {if let originalImage = defaultProcessor.process(item: .data(originalData), options: options) {originalCache.store(originalImage,original: originalData,forKey: key,processorIdentifier: defaultProcessor.identifier,cacheSerializer: options.cacheSerializer,toDisk: !options.cacheMemoryOnly,completionHandler: nil)}}}}if options.waitForCache == false || image == nil {completionHandler?(image, error, .none, url)}})}

对这个方法做个简单的总结。这个方法总主要做了三件事:

  • 调用了downloadImage方法,然后通过completionHandler这个回调函数把image, error,imageURL, originalData回调给我们。
  • 如果下载失败则从缓存中中查找图片
  • 拿到的图片数据存储起来,并且回调给外部使用

下面我们根据源码来分析作者如何实现这个三个功能的
图片下载:

    @discardableResultopen func downloadImage(with url: URL,retrieveImageTask: RetrieveImageTask? = nil,options: KingfisherOptionsInfo? = nil,progressBlock: ImageDownloaderProgressBlock? = nil,completionHandler: ImageDownloaderCompletionHandler? = nil) -> RetrieveImageDownloadTask?{// 如果这个任务在开始之前就已经被取消则返回nilif let retrieveImageTask = retrieveImageTask, retrieveImageTask.cancelledBeforeDownloadStarting {completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCancelledBeforeStarting.rawValue, userInfo: nil), nil, nil)return nil}// 外界如果给了超时时间没给就15slet timeout = self.downloadTimeout == 0.0 ? 15.0 : self.downloadTimeout// We need to set the URL as the load key. So before setup progress, we need to ask the `requestModifier` for a final URL.var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeout)request.httpShouldUsePipelining = requestsUsePipeliningif let modifier = options?.modifier {guard let r = modifier.modified(for: request) else {completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCancelledBeforeStarting.rawValue, userInfo: nil), nil, nil)return nil}request = r}// There is a possibility that request modifier changed the url to `nil` or empty.guard let url = request.url, !url.absoluteString.isEmpty else {completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.invalidURL.rawValue, userInfo: nil), nil, nil)return nil}var downloadTask: RetrieveImageDownloadTask?
// setup是一个任务管理队列,里面用了semphore作为锁去保证线程安全,spinlock会造成死锁。setup(progressBlock: progressBlock, with: completionHandler, for: url, options: options) {(session, fetchLoad) -> Void inif fetchLoad.downloadTask == nil {let dataTask = session.dataTask(with: request)fetchLoad.downloadTask = RetrieveImageDownloadTask(internalTask: dataTask, ownerDownloader: self)dataTask.priority = options?.downloadPriority ?? URLSessionTask.defaultPriorityself.delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request)dataTask.resume()// Hold self while the task is executing.self.sessionHandler.downloadHolder = self}fetchLoad.downloadTaskCount += 1downloadTask = fetchLoad.downloadTaskretrieveImageTask?.downloadTask = downloadTask}return downloadTask}

返回的RetrieveImageDownloadTask暴露给外部可以让下载中的任务取消

从缓存查找图片

    // MARK: - Get data from cache/**Get an image for a key from memory or disk.- parameter key:               Key for the image. - 查找图片的URL- parameter options:           Options of retrieving image. If you need to retrieve an image which was stored with a specified `ImageProcessor`, pass the processor in the option too.- parameter completionHandler: Called when getting operation completes with image result and cached type of this image. If there is no such key cached, the image will be `nil`.- returns: The retrieving task.*/@discardableResultopen func retrieveImage(forKey key: String,options: KingfisherOptionsInfo?,completionHandler: ((Image?, CacheType) -> Void)?) -> RetrieveImageDiskTask?{// No completion handler. Not start working and early return.guard let completionHandler = completionHandler else {return nil}var block: RetrieveImageDiskTask?let options = options ?? KingfisherEmptyOptionsInfolet imageModifier = options.imageModifier// 从内存中查找图片, key为图片的url,未处理if let image = self.retrieveImageInMemoryCache(forKey: key, options: options) {options.callbackDispatchQueue.safeAsync {// 把查找结果回调出去- completionHandler(imageModifier.modify(image), .memory)}} else if options.fromMemoryCacheOrRefresh { // Only allows to get images from memory cache.options.callbackDispatchQueue.safeAsync {completionHandler(nil, .none)}} else {var sSelf: ImageCache! = selfblock = DispatchWorkItem(block: {// Begin to load image from disk// 从磁盘查找图片,key在这里为图片的URL经过md5处理。if let image = sSelf.retrieveImageInDiskCache(forKey: key, options: options) {// 后台解码 - if options.backgroundDecode {sSelf.processQueue.async {// 位图上下文中解析图片let result = image.kf.decoded// 存储查找到的图片sSelf.store(result,forKey: key,processorIdentifier: options.processor.identifier,cacheSerializer: options.cacheSerializer,toDisk: false,completionHandler: nil)// 主线程直接回调options.callbackDispatchQueue.safeAsync {completionHandler(imageModifier.modify(result), .disk)// 释放资源sSelf = nil}}} else {// 存储图片sSelf.store(image,forKey: key,processorIdentifier: options.processor.identifier,cacheSerializer: options.cacheSerializer,toDisk: false,completionHandler: nil)options.callbackDispatchQueue.safeAsync {completionHandler(imageModifier.modify(image), .disk)sSelf = nil}}} else {// No image found from either memory or diskoptions.callbackDispatchQueue.safeAsync {completionHandler(nil, .none)sSelf = nil}}})sSelf.ioQueue.async(execute: block!)}return block}

存储图片

    /**Store an image to cache. It will be saved to both memory and disk. It is an async operation.- parameter image:             将要存储的图片- parameter original:          图片的data,将会跟图片转成的data对比,用来获取图片类型.如果为空则会存储一个png类型的图片.- parameter key:               存储图片的key.- parameter identifier:       用来处理图片的一个标识符.- parameter toDisk:            是否存储到磁盘,如果是false则仅仅存储在缓存内.- parameter completionHandler: 结果回调.*/open func store(_ image: Image,original: Data? = nil,forKey key: String,processorIdentifier identifier: String = "",cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default,toDisk: Bool = true,completionHandler: (() -> Void)? = nil){// 如果ID为空, 则直接返回key,否则拼接上IDlet computedKey = key.computedKey(with: identifier)memoryCache.setObject(image, forKey: computedKey as NSString, cost: image.kf.imageCost)func callHandlerInMainQueue() {if let handler = completionHandler {DispatchQueue.main.async {handler()}}}if toDisk {// ioQueue 保证数据操作安全  ioQueue.async {// 存储到磁盘// 根据image获取图片的data。如果未传入data,则所有图片按照png类型,转为data。data主要用来判断图片格式, 通过image生成一个dataif let data = serializer.data(with: image, original: original) {//  文件存储在cache中(iOS文件结构: Documents, Library:(Cache , Preference) Tmp)if !self.fileManager.fileExists(atPath: self.diskCachePath) {do {try self.fileManager.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true, attributes: nil)} catch _ {}}//  cachePath(), 存储路径,对文件名称进行md5处理。可以保证唯一性和长度固定等self.fileManager.createFile(atPath: self.cachePath(forComputedKey: computedKey), contents: data, attributes: nil)}
// 存储结束通知外面callHandlerInMainQueue()}} else {callHandlerInMainQueue()}}

二 、接下来我们看第二个图片加载策略:

tryToRetrieveImageFromCache

看完第一个第二个基本不用看,第二个和第一个主要区别是第一个直接下载。第二个会先从缓存中查找,找不到再进行第一步的操作

至此图片的的下载和缓存已经大概介绍完了

总结:

  • 根据URL去加载图片
  • 先从缓存查找 key为图片URL
  • 磁盘查找, 找到后在存入缓存中key为图片URL经过md5处理
  • 未找到下载图片,下载完成后存入缓存和磁盘。磁盘操作有一个队列串形处理

Kingfisher基本入门介绍相关推荐

  1. .NET读写Excel工具Spire.Xls使用(1)入门介绍

    原文:[原创].NET读写Excel工具Spire.Xls使用(1)入门介绍 在.NET平台,操作Excel文件是一个非常常用的需求,目前比较常规的方法有以下几种: 1.Office Com组件的方式 ...

  2. 独家 | 集成学习入门介绍

    作者:Jason Brownlee 翻译:wwl 校对:王琦 本文约3300字,建议阅读8分钟. 本文介绍了我们在生活中的许多决定包括了其他人的意见,由于群体的智慧,有的时候群体的决策优于个体.在机器 ...

  3. SpringBoot 2.0 系列001 -- 入门介绍以及相关概念

    为什么80%的码农都做不了架构师?>>>    SpringBoot 2.0 系列001 -- 入门介绍以及相关概念 什么是SpringBoot? 项目地址:http://proje ...

  4. ECC加密算法入门介绍

    作者  : ZMWorm[CCG]   E-Mail: zmworm@sohu.com   主页  : Http://ZMWorm.Yeah.Net/ 前言 同RSA(Ron Rivest,Adi S ...

  5. [翻译][1.4.2]Flask-Admin入门介绍

    为什么80%的码农都做不了架构师?>>>    #Flask-Admin入门介绍 ##让我们荡起双桨 初始化 Introduction To Flask-Admin Getting ...

  6. 谷歌大脑科学家亲解 LSTM:一个关于“遗忘”与“记忆”的故事 本文作者:奕欣 2017-01-14 09:46 导语:AI科技评论保证这是相对通俗易懂的一篇入门介绍了,看不懂的话欢迎关注「AI 科技

    谷歌大脑科学家亲解 LSTM:一个关于"遗忘"与"记忆"的故事 本文作者:奕欣 2017-01-14 09:46 导语:AI科技评论保证这是相对通俗易懂的一篇入 ...

  7. Tomcat容器入门介绍

    Tomcat容器入门介绍 Tomcat环境配置 PS:JDK的安装这里就不讲了,找到安装包直接下一步下一步就行了. 1.配置JDK 在Windows10下,找到环境变量 在环境变量中添加JDK主目录 ...

  8. QWT中Qdial的入门介绍

    最近使用了一下QWT.因为是第一次使用,所以有一些需要注意的地方,特记录在此,以供后来者参考. 1,QWT的安装与配置环境 有关QWT的安装与配置,网络上已经有很多篇文章,这里就不再重复了.介绍一下自 ...

  9. Spring入门介绍:

    Spring入门介绍 Spring诞生: 创建Spring的目的就是用来替代更加重量级的的企业级Java技术 简化Java的开发 基于POJO轻量级和最小侵入式开发 通过依赖注入和面向接口实现松耦合 ...

最新文章

  1. 因为BitMap,白白搭进去8台服务器...
  2. 【转载】深度学习数学基础(二)~随机梯度下降(Stochastic Gradient Descent, SGD)
  3. TCP丢包检测技术详解
  4. C++中的抽象类及纯虚函数的实现与否
  5. c语言专属英语单词,C语言 V 编程英语单词.doc
  6. yum install mysql-server 指定版本_mysql 指定版本安装
  7. java程序员中英文简历_2017java程序员英文简历范文
  8. 如何彻底卸载3dmax2020_3DMax如何才能彻底卸载干净啊?
  9. 专访「算法之父」Michael Saunders:人工智能未来的突破点可能在自动驾驶
  10. 厦大C语言上机 1357 小明的考题2――数与单词
  11. 哪个更好:Revo卸载程序或免费替代方案?
  12. 凝思系统服务器系统版本,凝思操作系统Custom Linx安装教程
  13. java程序求内切圆_JAVA求正方形边长,圆内切正方形,圆的直径为8,求正方形边长和面积!...
  14. 加快MATLAB运行速度的三个方法
  15. 【定义】向量与向量组
  16. python第三方包安装方法
  17. NEC the WISE点亮的不仅仅是新零售
  18. 二维码生成和解码(二)
  19. STC15F2K60S2内E2PROM应用
  20. 数字化转型导师坚鹏:银行如何建设行业领先的人才培训管理体系

热门文章

  1. 人工智能入门必须攻克三道门槛:数学基础、英语水平与编程技术
  2. Android:自定义View实现签名带笔锋效果
  3. 广告太多超烦人?让你和烦人的弹窗广告说拜拜!
  4. Centos安装XSS平台
  5. 知乎高赞 | 区块链是什么? (一)
  6. 寻找三个整数中的“中”数
  7. 手把手教你写一个没有服务器的颜值打分小程序,可直接上线
  8. 这个岗位年薪几十万,为何各大科技企业仍招不到人?
  9. Fuzzing论文:Reinforcement Learning-based Hierarchical Seed Scheduling for Greybox Fuzzing
  10. 秋招快报 | 唯品会、百度2019秋招网申今日截止!