Kingfisher
是喵神开源的一个图片加载框架,能够加载网络,本地的多种格式的图片,并提供了缓存和动画的支持,随着 SwiftUI 的发布,Kingfisher
也增加了对 SwiftUI 的扩展。最近的 iOS Widget 项目中正好使用了 SwiftUI,借此机会学习一下 Kingfisher 各个部分的实现以及对 SwiftUI 的封装。
此处使用的版本
Github address: Kingfiher
Tag: 5.15.7
Kingfisher 的主要模块
kf 命名空间
在使用 Kingfisher
的时候我们能发现所有的 API 调用都会使用"前缀" kf
来调用,例如 UIImageview
等可以调用 API 的组件都通过扩展拥有了一个 kf
的计算属性。然后再通过 kf 调用指定的 API,这是在 Swift 中写 Extension 时很流行的写法。
在 OC 当中,我们在写扩展(分类)的时候,会在扩展方法前面加上一个 xx_
样式的前缀,来标明这是一个自定义的扩展。这样做的好处有两个,第一,避免语言或系统升级更新后,系统 API 与你自定义的扩展重名导致冲突, 第二,使用前缀,当你想要调用扩展方法时,Xcode 的语法提示能替你过滤掉其余选项 。
在 Swift 发布以后,早期还是使用了上述的方法,后来利用 Swift 灵活的协议和泛型系统,这种更为优雅的实现就流行开来,Kingfisher
中对 kf
的实现很简单,如下可见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public struct KingfisherWrapper <Base > { public let base: Base public init (_ base : Base ) { self .base = base } } public protocol KingfisherCompatible : AnyObject { }public protocol KingfisherCompatibleValue {}extension KingfisherCompatible { public var kf: KingfisherWrapper <Self > { get { return KingfisherWrapper (self ) } set { } } } extension KingfisherCompatibleValue { public var kf: KingfisherWrapper <Self > { get { return KingfisherWrapper (self ) } set { } } } extension KFCrossPlatformImageView : KingfisherCompatible { }
我们从一个简单的例子来看 imageView.kf.setImage(xxxx)
, 可知 kf
是一个扩展属性,为了方便各个类型都能调用这个属性,需要一个协议 KingfisherCompatible
和 KingfisherCompatibleValue
(在 Kingfisher 对 class 约定了一个特殊的协议,正常情况下一个也行), 在这两个协议中我们默认实现了 kf
这个计算属性,这样任意的 Object 和 Value 都能够通过遵守协议实现 kf
属性。Kingfisher 中的 extension KFCrossPlatformImageView: KingfisherCompatible { }
, KFCrossPlatformImageView
在 iOS 中就是 UIImageView
,这样我们就能直接调用 imageView.kf
。
而获取到的 kf
属性,实际上就是一个包含了调用者自己的 Wrapper,看 KingfisherWrapper
的定义可知,这个结构体只是将初始化的值保存起来。这样当我们对 KingfisherWrapper
编写扩展方法时,就能通过 base
属性获取到原本需要使用的值或对象 (imageView.kf.base == imageView
)。
最重要的就是 Swift 对泛型约束的支持,显然,我们所有的扩展都是针对 KingfisherWrapper
这个结构体的,但实际上我们编写的扩展是针对 KingfisherWrapper
中的 Base
, 我们肯定不希望任意有 kf
属性的对象都能调用所有的扩展。而编写扩展的时候,我们也需要确定 Base
的类型。通过泛型约束我们就能轻松解决上述的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 extension KingfisherWrapper where Base : UIButton { @discardableResult public func setImage ( with source : Source ?, for state : UIControl .State , placeholder : UIImage ? = nil , options : KingfisherOptionsInfo ? = nil , progressBlock : DownloadProgressBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { } }
这是 Kingfisher 对 UIButton
的一个扩展方法,通过把泛型参数 Base
限定为 UIButton
, 我们在内部实现扩展方法时,base
属性会被编译器推断为 UIButton
我们就不需要再进行类型判断和转换了。而在使用的这个方法时,我们写出 button.kf.
时,Xcode 能通过类型推断只显示此类型能够使用的扩展方法,很完美。
Kingfisher 中的主要部分
代码结构
General: 命名空间、错误类型、资源类型等基础类型的定义
Image: 对 Image 的封装,格式转换,解码,GIF解析
Networking: 网络图片的下载
Cache: 图片的缓存
Views: 内置的进度条和动画 View 视图
Extensions: 对 ImageView、Button、WKInterfaceImage等对象的扩展,常调用的 API 就源与此
Utility: 一些帮助方法,字符串的MD5、Runtime、调用栈之类的
SwiftUI: 针对 SwiftUI 进行的扩展
从一个例子开始解析
最普通的调用
知道了 Kingfisher 的主要结构以后,我们就从最常用的调用开始,一步步深入并研究途中遇到的各种参数和类型。最简单也是最常用的就是为 ImageView 设置一个图片,代码如下:
1 2 3 let url = URL (string: "https://example.com/image.png" )imageView.kf.setImage(with: .network(url))
此方法定义在 ImageView+Kingfisher.swift
当中,我们先来看它的定义:
1 2 3 4 5 6 7 8 9 @discardableResult public func setImage ( with source : Source ?, placeholder : Placeholder ? = nil , options : KingfisherOptionsInfo ? = nil , progressBlock : DownloadProgressBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { }
首先,我们解决最简单的 progressBlock
和 completionHandler
, DownloadProgressBlock
的定义为 ((_ receivedSize: Int64, _ totalSize: Int64) -> Void)
, 只是一个图片下载进度的回调,然后 completionHandler
是图片下载完成的回调,Result 如果是 success 将返回一个 RetrieveImageResult
,这是一个结构体,里面有一个 image
属性可以获取图片,如果失败就返回 KingfisherError
类型的错误信息。
其次是 placeholder
, 它不单单是一个占位图,此处的 Placeholder
是一个协议,任何遵守协议的对象都可以作为占位传入,它的定义如下:
1 2 3 4 5 6 public protocol Placeholder { func add (to imageView : KFCrossPlatformImageView ) func remove (from imageView : KFCrossPlatformImageView ) }
这地方体现了面向协议编程的精髓,你无须去创造一个类型,而是描述你所需要的类型的行为,为了减轻实现的难度,这会迫使你尽可能的简化非必要的行为,最后留下的就是纯粹的定义,越是简单就越是灵活,bug 也会更少。就如同数学和物理中的公理一样,越是简单越是稳固,这样上层构建的软件才更加牢固, 此处的这个协议就精准的描述了一个 Placeholder 需要实现的所有行为,如何被添加,如何被删除。
定义资源 Source
然后是 source
, 此参数顾名思义是提供图片的来源, 查看 Source
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 public enum Source { case network(Resource ) case provider(ImageDataProvider ) public var cacheKey: String { } public var url: URL ? { } }
可以看到 Source
是一个 Enum, 它有两个类型,一个是.network(Resource)
代表网络资源,而 .provider(ImageDataProvider)
则表示任何能够提供图片的方式,比如本地图片加载,或是通过编码字符串(base64)获取等。
我们先来看 .network(Resource)
, 这里说一下在最上面的的例子中,我们常用的是 imageView.kf.setImage(with: url)
, 它的实现是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @discardableResult public func setImage ( with resource : Resource ?, placeholder : Placeholder ? = nil , options : KingfisherOptionsInfo ? = nil , progressBlock : DownloadProgressBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { return setImage( with: resource? .convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) }
可以发现我们传入的 Resource
会通过 resource?.convertToSource()
转换为 Source
类型, 实际的调用还是我们上面分析的那个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public protocol Resource { var cacheKey: String { get } var downloadURL: URL { get } } extension Resource { public func convertToSource () -> Source { return downloadURL.isFileURL ? .provider(LocalFileImageDataProvider (fileURL: downloadURL, cacheKey: cacheKey)) : .network(self ) } } extension URL : Resource { public var cacheKey: String { return absoluteString } public var downloadURL: URL { return self } }
以上是 Resource
的定义,一个远程资源需要有下载地址 downloadURL
和缓存 key cacheKey
, 我们能直接传入 URL 类型是因为 URL 遵守了 Resource
的协议。
现在可以看出 imageView.kf.setImage(with: url)
内部会被转换为 imageView.kf.setImage(with: .network(url))
. Resource
作为一个协议能更方便扩展其他类型的数据作为资源来使用,这在实际项目当中很有用,我们能直接扩展项目中的 Model 作为 Resource
传入。
接下来看 Source 的另一个值 .provider(ImageDataProvider)
, ImageDataProvider
的定义如下:
1 2 3 4 5 public protocol ImageDataProvider { var cacheKey: String { get } func data (handler : @escaping (Result <Data , Error >) -> Void ) var contentURL: URL ? { get } }
ImageDataProvider
中主要约定了一个资源的缓存 key,和一个获取图片数据的方法, 在我们上面的例子中,当传入的 url 地址是本地地址时,会被转换为 LocalFileImageDataProvider
, 这是一个本地图片的 ImageDataProvider 的实现,主要实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public struct LocalFileImageDataProvider : ImageDataProvider { public let fileURL: URL public init (fileURL : URL , cacheKey : String ? = nil ) { self .fileURL = fileURL self .cacheKey = cacheKey ?? fileURL.absoluteString } public var cacheKey: String public func data (handler : (Result <Data , Error >) -> Void ) { handler(Result (catching: { try Data (contentsOf: fileURL) })) } public var contentURL: URL ? { return fileURL } }
本地图片的路径被作为 Cache Key, 图片的获取方法,就是加载本地图片文件的数据,这就是本地图片的 Provider 实现。
图片加载的配置
最后我们来看参数 options
, 他的类型定义是 public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]
, options 中包含了图片加载的动画,动画样式,缓存管理,执行线程等等各种设置, KingfisherOptionsInfoItem
是一个枚举,里面列举了各种各样的设置参数,最后这个包含设置的数组会被转换为 KingfisherParsedOptionsInfo
这是一个结构体,属性就是 KingfisherOptionsInfoItem
所有的枚举值. 这里面的设置参数控制了很多的东西,继续往里分析,等遇到的时候,我们再来关注他们的作用.
返回值 DownloadTask
是一个下载任务,可以用来取消下载任务,暂且不细看它了。
接下来我们仔细研究一下此方法的实现,里面应该包含了 ImageView 加载图片的过程。因为实现很长,我们分段解析,开头如下:
1 2 3 4 5 6 7 var mutatingSelf = self guard let source = source else { mutatingSelf.placeholder = placeholder mutatingSelf.taskIdentifier = nil completionHandler? (.failure(KingfisherError .imageSettingError(reason: .emptySource))) return nil }
这段代码主要是对 source
的值进行判空,如果值为 nil 就直接返回错误,并设置一些属性。
第一行就值得分析一下,此处的 self
的类型就是 KingfisherWrapper
, 我们知道结构体内的方法改变属性需要使用 mutating
标记。而此处作者使用 var mutatingSelf = self
来复制一个 mutating value. 为什么不是将方法标记为 mutating
然后直接修改属性呢? 稍作思考,我们应该知道改变一个结构体的属性,除了方法需要标记为 mutating
以外,这个结构体也需要是可变的(mutating value), 而我们通过 imageView.kt
只读属性拿到的是一个 immutable value
,即使将方法标记为 mutating
也无法调用,会报 error: cannot use mutating member on immutable value: 'kt' is a get-only property
的错误。
不过这里的 mutatingSelf
作为值类型是复制的,我们对复制的 mutatingSelf 进行属性修改,并不会修改原来 self 中的值啊? 这里我们需要看一下赋值的属性是如何定义的:
1 2 3 4 5 6 7 8 9 10 public private(set) var taskIdentifier: Source .Identifier .Value ? { get { let box: Box <Source .Identifier .Value >? = getAssociatedObject(base, & taskIdentifierKey) return box? .value } set { let box = newValue.map { Box ($0 ) } setRetainedAssociatedObject(base, & taskIdentifierKey, box) } }
taskIdentifier
是对 KingfisherWrapper
扩展的计算属性,而它的本质是对内部的 base 进行赋值操作, 使用 Runtime
机制我们可以动态的为对象添加属性和方法,这里就不在展开,使用 Box
来包装值,只是为了将值类型包装为对象方便进行存储。
看到这里我们发现,对 mutaingSelf 的复制操作,本质上是在操作内部的 base 对象,而 base 的类型是 KFCrossPlatformImageView
这是一个引用类型, 值类型复制时,并不会拷贝内部的引用属性,mutatingSelf 中的 base 和 self 中的 base 还是同一个对象。那上述代码实际上都是对同一个 base 进行赋值操作, 所以这样写是没有问题的。
继续往下看:
1 2 3 4 5 6 var options = KingfisherParsedOptionsInfo (KingfisherManager .shared.defaultOptions + (options ?? .empty))let isEmptyImage = base.image == nil && self .placeholder == nil if ! options.keepCurrentImageWhileLoading || isEmptyImage { mutatingSelf.placeholder = placeholder }
第一行通过配置参数生成了配置信息,这里出现的 KingfisherManager
是一个管理类,里面包含了对默认配置参数,图片下载器,缓存相关的配置,这里先记住它的大致作用就行了。后续的代码,是根据配置信息设置 placeholder
的值。
1 2 let maybeIndicator = indicatormaybeIndicator? .startAnimatingView()
这里打开图片的加载动画,indicator
也是一个扩展属性,它同样是通过 runtime
存储在 base(imageView) 当中的。他的类型 Indicator
是一个协议,约定了指示器的行为,具体可以查看定义。
继续看:
1 2 3 4 5 6 let issuedIdentifier = Source .Identifier .next()mutatingSelf.taskIdentifier = issuedIdentifier if base.shouldPreloadAllAnimation() { options.preloadAllAnimationData = true }
我们在看 Source 的时候,忽略了内部定义的 Identifier
, 我们查看的它的定义:
1 2 3 4 5 6 7 8 9 10 public enum Identifier { public typealias Value = UInt static var current: Value = 0 static func next () -> Value { current += 1 return current } }
它其实就是一个迭代器, 在 App 的声明周期内用来生成自增的 UInt 数值,不重复的数值用来表示当前加载图片的任务 ID,我猜测后续的进度和状态通知会用到这个 ID。接下来是设置了一个 preloadAllAnimationData
的属性,应该是动画相关的处理,后续再看。
然后就是进度和状态相关的回调设置了,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect (block)] } if let provider = ImageProgressiveProvider (options, refresh: { image in self .base.image = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived? .forEach { $0 .onShouldApply = { issuedIdentifier == self .taskIdentifier } }
根据字面意思,开头的 block
应该是进度监听的,provider
是图片加载的回调 (并不是图片下载完后设置图片,而是图片下载过程中,显示部分图片,图片数据不全时也可以先显示已有数据的图片,有很多的加载方法,有如从上往下逐行扫描加载,也有分隔显示数据,在逐步填充等等)。重点关注 onDataReceived
,点进去可以看到它的定义是 var onDataReceived: [DataReceivingSideEffect]? = nil
, 它用来存储接收到图片数据时,会触发的各种"副作用",这里的副作用是指用与图片下载事务本身无关,只是为了满足外界的状态处理,进而在获取到图片数据时,通知所有的监听者。我们来看看 DataReceivingSideEffect
是如何定义的:
1 2 3 4 protocol DataReceivingSideEffect : AnyObject { var onShouldApply: () -> Bool { get set } func onDataReceived (_ session : URLSession , task : SessionDataTask , data : Data ) }
定义很简单,onShouldApply
说明此条副作用是否还有效,为什么定义为 block
,我们后续再看。onDataReceived
就是每次获取到最新的 data 时的调用了。我们再来看最后一句:
1 2 3 options.onDataReceived? .forEach { $0 .onShouldApply = { issuedIdentifier == self .taskIdentifier } }
循环设置了 onDataReceived 的 onShouldApply 属性,在上面的分析中,我们知道每次执行新的任务都会生成一个新的 taskIdentifier, 这个 id 实际是设置到了 imageView 的扩展属性中去了, 到这里就能明白为什么使用 block 而不是普通的值了, 每次调用 block 都是和 imageView 当前的 taskIdentifier 进行对比,比如一个 imageView 短时间重复设置加载图片,只有最后一个加载任务的进度和数据信息会被传递出去,防止多个加载任务之间的信息错乱。
上述都是进行相关的设置,接下就是图片的下载部分了,代码比较长,我直接在代码中标注相关的意思。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 let task = KingfisherManager .shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue .mainCurrentOrAsync.execute { maybeIndicator? .stopAnimatingView() guard issuedIdentifier == self .taskIdentifier else { let reason: KingfisherError .ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil , source: source) } catch { reason = .notCurrentSourceTask(result: nil , error: error, source: source) } let error = KingfisherError .imageSettingError(reason: reason) completionHandler? (.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): guard self .needsTransition(options: options, cacheType: value.cacheType) else { mutatingSelf.placeholder = nil self .base.image = value.image completionHandler? (result) return } self .makeTransition(image: value.image, transition: options.transition) { completionHandler? (result) } case .failure: if let image = options.onFailureImage { self .base.image = image } completionHandler? (result) } } } ) mutatingSelf.imageTask = task return task
以上我们逐行分析了 imageView.kf.setImage(with: url)
中,图片下载和设置的流程。现在大致对整个流程有了了解,接下来我们逐个分析其中使用的模块,首先我们从 KingfisherManager
入手,它整合了 Kingfisher 中的各个模块。
更进一步 KingfisherManager
在上面的例子中,我们核心是调用 KingfisherManager 中 retrievingImage
方法来加载图片的,我们就从这里开始入口,逐行了解相关的作用:
在分析代码之前,我是有一个疑问的,downloadTaskUpdated 用来更新 task 是为什么,调用 retrievingImage 时返回的 task 会发生变化吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 func retrieveImage ( with source : Source , options : KingfisherParsedOptionsInfo , downloadTaskUpdated : DownloadTaskUpdatedBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> DownloadTask ? { let retrievingContext = RetrievingContext (options: options, originalSource: source) var retryContext: RetryContext ? func startNewRetrieveTask ( with source : Source , downloadTaskUpdated : DownloadTaskUpdatedBlock ? ) { let newTask = self .retrieveImage(with: source, context: retrievingContext) { result in handler(currentSource: source, result: result) } downloadTaskUpdated? (newTask) } func failCurrentSource (_ source : Source , with error : KingfisherError ) { guard ! error.isTaskCancelled else { completionHandler? (.failure(error)) return } if let nextSource = retrievingContext.popAlternativeSource() { startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated) } else { if retrievingContext.propagationErrors.isEmpty { completionHandler? (.failure(error)) } else { retrievingContext.appendError(error, to: source) let finalError = KingfisherError .imageSettingError( reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors) ) completionHandler? (.failure(finalError)) } } } func handler (currentSource : Source , result : (Result <RetrieveImageResult , KingfisherError >)) -> Void { switch result { case .success: completionHandler? (result) case .failure(let error): if let retryStrategy = options.retryStrategy { let context = retryContext? .increaseRetryCount() ?? RetryContext (source: source, error: error) retryContext = context retryStrategy.retry(context: context) { decision in switch decision { case .retry(let userInfo): retryContext? .userInfo = userInfo startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated) case .stop: failCurrentSource(currentSource, with: error) } } } else { guard ! error.isTaskCancelled else { completionHandler? (.failure(error)) return } if let nextSource = retrievingContext.popAlternativeSource() { retrievingContext.appendError(error, to: currentSource) startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated) } else { if retrievingContext.propagationErrors.isEmpty { completionHandler? (.failure(error)) } else { retrievingContext.appendError(error, to: currentSource) let finalError = KingfisherError .imageSettingError( reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors) ) completionHandler? (.failure(finalError)) } } } } } return retrieveImage( with: source, context: retrievingContext) { result in handler(currentSource: source, result: result) } }
在以上的方法中,我们基本上已经清楚了检索图片时,成功后的处理,失败及失败后的重试操作,还有替代资源的加载。
RetrievingContext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class RetrievingContext { var options: KingfisherParsedOptionsInfo let originalSource: Source var propagationErrors: [PropagationError ] = [] init (options : KingfisherParsedOptionsInfo , originalSource : Source ) { self .originalSource = originalSource self .options = options } func popAlternativeSource () -> Source ? { guard var alternativeSources = options.alternativeSources, ! alternativeSources.isEmpty else { return nil } let nextSource = alternativeSources.removeFirst() options.alternativeSources = alternativeSources return nextSource } @discardableResult func appendError (_ error : KingfisherError , to source : Source ) -> [PropagationError ] { let item = PropagationError (source: source, error: error) propagationErrors.append(item) return propagationErrors } }
顺着上面的代码继续往下,最后调用的 retrieveImage
的定义是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private func retrieveImage ( with source : Source , context : RetrievingContext , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> DownloadTask ? { let options = context.options if options.forceRefresh { return loadAndCacheImage( source: source, context: context, completionHandler: completionHandler)? .value } else { let loadedFromCache = retrieveImageFromCache( source: source, context: context, completionHandler: completionHandler) if loadedFromCache { return nil } if options.onlyFromCache { let error = KingfisherError .cacheError(reason: .imageNotExisting(key: source.cacheKey)) completionHandler? (.failure(error)) return nil } return loadAndCacheImage( source: source, context: context, completionHandler: completionHandler)? .value } }
上面的代码的逻辑很清晰,就是根据配置,看是获取图片还是读取缓存。这里的两个核心方法,一个是获取图片 loadAndCacheImage
, 另一个是 retrieveImageFromCache
, 我们先从 loadAndCacheImage 入手,看看如何加载图片的:
loadAndCacheImage 加载图片并缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @discardableResult func loadAndCacheImage ( source : Source , context : RetrievingContext , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> DownloadTask .WrappedTask ? { let options = context.options func _cacheImage (_ result : Result <ImageLoadingResult , KingfisherError >) { cacheImage( source: source, options: options, context: context, result: result, completionHandler: completionHandler ) } switch source { case .network(let resource): let downloader = options.downloader ?? self .downloader let task = downloader.downloadImage( with: resource.downloadURL, options: options, completionHandler: _cacheImage ) if let task = task { return .download(task) } else { return nil } case .provider(let provider): provideImage(provider: provider, options: options, completionHandler: _cacheImage) return .dataProviding } }
根据资源的类型,首先是加载网络图片,获取到配置的下载器,开始下载图片并返回对应的 task。其中使用的 downloader 作为一个较为独立的模块,后续可以独立分析。而通过 provider 获取 image 内部的代码是很简单了,ImageDataProvider
这个协议上面我们讲到过,内部通过 data(handler: @escaping (Result<Data, Error>) -> Void)
方法来获取图片,除了我们自定义以外,本地图片的获取方法就是加载本地地址而已,就不多做赘述了。
而 retrieveImageFromCache
的大致行为也就是从缓存中检索图片,代码也不复杂,到现在我们至少已经了解了 Kingfisher 中图片加载和各种配置的处理操作流程,剩下的两个主要功能是非常独立的,我们将分别深入的探查它们的实现细节。