六游的博客小站
Kingfisher源码阅读
发布于: 2020-10-18 更新于: 2020-10-18 阅读次数: 

本篇文章为Kingfisher源码导读,不会详细分析源码中的每一个细节,只是分享我阅读源码时的顺序以及思路,按照文中的代码阅读顺序加上你自己主动阅读源码的一些理解,读懂整个Kingfisher系统内部的工作原理应该不是很难的事情。

Source

Source表示一个图片资源的来源。KF中图片拥有两种来源,一个是来源于网络(network),一个是来源于本地(provider)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Source {
case network(Resource)
case provider(ImageDataProvider)

public var cacheKey: String {
switch self {
case .network(let resource): return resource.cacheKey
case .provider(let provider): return provider.cacheKey
}
}
public var url: URL? {
switch self {
case .network(let resource): return resource.downloadURL
case .provider(let provider): return provider.contentURL
}
}
}

Resource与ImageDataProvider是两个协议类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
}
}
public protocol ImageDataProvider {
// 用于缓存的键
var cacheKey: String { get }
// 用于外部读取文件资源的方法
func data(handler: @escaping (Result<Data, Error>) -> Void)
// URL
var contentURL: URL? { get }
}

看完了协议类型,再来看对应的两个实现会更加容易理解一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第一个是我们日常使用中最常用到的,直接使用URL作为参数来加载一个网络图片
extension URL: Resource {
public var cacheKey: String { return absoluteString }
public var downloadURL: URL { return self }
}
// 第二个是实现了ImageDataProvider协议的本地图片加载器
public struct LocalFileImageDataProvider: ImageDataProvider {
public let fileURL: URL
public var cacheKey: String
public init(fileURL: URL, cacheKey: String? = nil) {
self.fileURL = fileURL
self.cacheKey = cacheKey ?? fileURL.absoluteString
}
// data方法很简单,就是直接读取对应路径的文件,并将读取的结果通过闭包回调出去
public func data(handler: (Result<Data, Error>) -> Void) {
handler(Result(catching: { try Data(contentsOf: fileURL) }))
}

public var contentURL: URL? {
return fileURL
}
}

入口

为了能够满足跨平台的需求,KF定义了一系列类型别名,并且结合条件编译指令,在不同的平台上为这一系列类型别名绑定不同的类型。是非常值得学习的一个做法。此次源码分析我们以iOS平台为例,在iOS平台中这些类型别名存在以下对应关系

KFCrossPlatformImage -> UIImage

KFCrossPlatformColor -> UIColor

KFCrossPlatformImageView -> UIImageView

KFCrossPlatformView -> UIView

KFCrossPlatformButton -> UIButton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#if os(macOS)
import AppKit
public typealias KFCrossPlatformImage = NSImage
public typealias KFCrossPlatformView = NSView
public typealias KFCrossPlatformColor = NSColor
public typealias KFCrossPlatformImageView = NSImageView
public typealias KFCrossPlatformButton = NSButton
#else
import UIKit
public typealias KFCrossPlatformImage = UIImage
public typealias KFCrossPlatformColor = UIColor
#if !os(watchOS)
public typealias KFCrossPlatformImageView = UIImageView
public typealias KFCrossPlatformView = UIView
public typealias KFCrossPlatformButton = UIButton
#else
import WatchKit
#endif
#endif

接着就是入口的代码

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
// 1.首先是定义了个空协议
public protocol KingfisherCompatible: AnyObject { }
// 2.然后让之前我们提到过的类型别名都实现这个空协议
extension KFCrossPlatformImage: KingfisherCompatible { }
#if !os(watchOS)
extension KFCrossPlatformImageView: KingfisherCompatible { }
extension KFCrossPlatformButton: KingfisherCompatible { }
#else
extension WKInterfaceImage: KingfisherCompatible { }
#endif
// 3.之后给这个空协议添加了个扩展
// 给协议添加扩展就等于给上面那些实现协议的类型添加扩展所以这三个步骤的整体结果就是:
// 给所有需要的类型添加了一个统一的扩展
// 扩展中提供了kf计算属性,在get中返回了一个KingfisherWrapper对象
// 这就是我们平时使用xxx.kf.setImage的奥秘所在
extension KingfisherCompatible {
public var kf: KingfisherWrapper<Self> {
get { return KingfisherWrapper(self) }
set { }
}
}
// 4. kf计算属性返回的KingfisherWrapper对象十分简单
// 就是对原对象的简单封装
// 如果我们是对UIImageView调用kf获取到的KingfisherWrapper对象,那么其中的base属性就是UIImageView对象本身
public struct KingfisherWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}

kf计算属性返回的KingfisherWrapper只是一个简单的包装器,本身不存储任何状态,并且在使用过后就会立即销毁,那么它是怎么完成那么复杂的操作(图片的下载,缓存,加载)的呢?其实KingfisherWrapper在工作的过程中是可以拿到一些持久存储的状态信息的,Kingfisher直接将一些状态以关联属性的方式存储在特定的Base中了,这里我们以UIImageView为例

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
// 关联属性存储时需要用到的Key
private var taskIdentifierKey: Void?
private var indicatorKey: Void?
private var indicatorTypeKey: Void?
private var placeholderKey: Void?
private var imageTaskKey: Void?

extension KingfisherWrapper where Base: KFCrossPlatformImageView {
// 存储当前图片下载任务的ID
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)
}
}

// KF允许你为还未加载完成的图片提供一个指示器
// 这个指示器有多种类型,该属性就是存储该UIImageView所使用的的指示器类型
public var indicatorType: IndicatorType {
get {
return getAssociatedObject(base, &indicatorTypeKey) ?? .none
}

set {
switch newValue {
case .none: indicator = nil
case .activity: indicator = ActivityIndicator()
case .image(let data): indicator = ImageIndicator(imageData: data)
case .custom(let anIndicator): indicator = anIndicator
}
setRetainedAssociatedObject(base, &indicatorTypeKey, newValue)
}
}

// 如果你指定了指示器类型为自定义指示器,那么可以通过该属性存储该UIImageView所使用的自定义指示器对象
public private(set) var indicator: Indicator? {
get {
let box: Box<Indicator>? = getAssociatedObject(base, &indicatorKey)
return box?.value
}

set {
// Remove previous
if let previousIndicator = indicator {
previousIndicator.view.removeFromSuperview()
}

if let newIndicator = newValue {
let view = newIndicator.view

base.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.centerXAnchor.constraint(
equalTo: base.centerXAnchor, constant: newIndicator.centerOffset.x).isActive = true
view.centerYAnchor.constraint(
equalTo: base.centerYAnchor, constant: newIndicator.centerOffset.y).isActive = true

switch newIndicator.sizeStrategy(in: base) {
case .intrinsicSize:
break
case .full:
view.heightAnchor.constraint(equalTo: base.heightAnchor, constant: 0).isActive = true
view.widthAnchor.constraint(equalTo: base.widthAnchor, constant: 0).isActive = true
case .size(let size):
view.heightAnchor.constraint(equalToConstant: size.height).isActive = true
view.widthAnchor.constraint(equalToConstant: size.width).isActive = true
}

newIndicator.view.isHidden = true
}

setRetainedAssociatedObject(base, &indicatorKey, newValue.map(Box.init))
}
}
// 当前UIImageView之下的图片加载任务
private var imageTask: DownloadTask? {
get { return getAssociatedObject(base, &imageTaskKey) }
set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)}
}

// 当图片还未加载完毕或者图片加载失败的时候,KF允许你提供一个Placeholder来填充空白的UIImageView
public private(set) var placeholder: Placeholder? {
get { return getAssociatedObject(base, &placeholderKey) }
set {
if let previousPlaceholder = placeholder {
previousPlaceholder.remove(from: base)
}

if let newPlaceholder = newValue {
newPlaceholder.add(to: base)
} else {
base.image = nil
}
setRetainedAssociatedObject(base, &placeholderKey, newValue)
}
}
}

接着就是KingfisherWrapper中的核心方法setImage了。这里以UIImageView为例(KFCrossPlatformImageView即对应UIImageView

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

extension KingfisherWrapper where Base: KFCrossPlatformImageView {
// 传入网络类型的Resource作为图片资源来源,是日常中最常用的方式,即通过URL传入
@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)
}
// 传入本地类型的ImageDataProvider作为图片资源来源
@discardableResult
public func setImage(
with provider: ImageDataProvider?,
placeholder: Placeholder? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
{
return setImage(
with: provider.map { .provider($0) },
placeholder: placeholder,
options: options,
progressBlock: progressBlock,
completionHandler: completionHandler)
}
// 前面两个方法都是对于不同source类型的简易封装,其内部都调用了一个共同的核心方法
// 就是下面这个setImage
@discardableResult
public func setImage(
with source: Source?,
placeholder: Placeholder? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
{
var mutatingSelf = self
// source判空,如果传入的source为nil,则视为图片加载失败
// 设置placeholder,清理task,并调用completion通知错误
guard let source = source else {
mutatingSelf.placeholder = placeholder
mutatingSelf.taskIdentifier = nil
completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource)))
return nil
}
// 合并全局options与当前方法参数的options
var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty))
// 有两种情况直接更新当前的placeholder:
// 第一种:options中设置为加载图片时并不需要保持当前图片
// 第二种:当前UIImageView中既没有当前图片,也没有placeholder,处于一个空白的状态
let isEmptyImage = base.image == nil && self.placeholder == nil
if !options.keepCurrentImageWhileLoading || isEmptyImage {
mutatingSelf.placeholder = placeholder
}

let maybeIndicator = indicator
maybeIndicator?.startAnimatingView()
// 获取下一个全局自增的taskIdentifier
let issuedIdentifier = Source.Identifier.next()
mutatingSelf.taskIdentifier = issuedIdentifier

if base.shouldPreloadAllAnimation() {
options.preloadAllAnimationData = true
}
// 如果参数中传入了progressBlock图片加载进度回调函数,那么将其合并到onDataReceived通知中去
if let block = progressBlock {
options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
}
// 负责监听图片数据加载,并对加载进来的数据进行解码,最后生成Image对象数据,并将生成的Image设为UIImageView的Image
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 }
}
// 通过KingfisherManager来生成图片加载的task
let task = KingfisherManager.shared.retrieveImage(
with: source,
options: options,
downloadTaskUpdated: { mutatingSelf.imageTask = $0 },
completionHandler: { result in
CallbackQueue.mainCurrentOrAsync.execute {
// 首先先停止自定义Indicator的动画
maybeIndicator?.stopAnimatingView()
// 如果task的Id与当前UIImageView中存储的ID不一致。则说明出现了错误
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):
// 判断是否有Transition过程,如果没有直接设置图片并清除placeholder
guard self.needsTransition(options: options, cacheType: value.cacheType) else {
mutatingSelf.placeholder = nil
self.base.image = value.image
completionHandler?(result)
return
}
// 如果有Transition过程,则通过UIView.transition执行动画过程
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
}
}

KingfisherManager

上面的setImage方法中除了对当前所操作的对象(如UIImageView)的状态进行一系列的更新操作之外,最重要的操作就是通过KingfisherManagerretrieve方法生成了图片加载任务

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

func retrieveImage(
with source: Source,
options: KingfisherParsedOptionsInfo,
downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
{
// 创建context上下文,方便多个层级之前传递信息
var retrievingContext = RetrievingContext(options: options, originalSource: source)
var retryContext: RetryContext?
// 封装重新生成一个新的DownloadTask的逻辑,通过调用另一个retrieveImage方法生成
// 并将该task更新的消息通过downloadTaskUpdated闭包发送出去
func startNewRetrieveTask(
with source: Source,
downloadTaskUpdated: DownloadTaskUpdatedBlock?
) {
let newTask = self.retrieveImage(with: source, context: retrievingContext) { result in
handler(currentSource: source, result: result)
}
downloadTaskUpdated?(newTask)
}
// 封装当一个DownloadTask加载图片失败,进行重试的逻辑
func failCurrentSource(_ source: Source, with error: KingfisherError) {
// 如果是用户主动关闭的加载而导致的失败,直接跳过,并回调错误
guard !error.isTaskCancelled else {
completionHandler?(.failure(error))
return
}
// 当前的source加载失败,尝试换源加载
// 从options中查找出了当前的source之外是否有另外可用的source
// 如果有则用新的source重新创建一个task
// 如果没有则输出错误
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 {
// 首先获取传递重试信息的context上下文
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
// 如果判断为可以进行重试当前Source,就调用之前我们封装好的逻辑
// 重新创建一个当前Source的DownloadTask
startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated)
// 如果判断为本source需要停止重试,那么就调用之前封装好的逻辑,尝试换源重试
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 {
// No other alternative source. Finish with error.
if retrievingContext.propagationErrors.isEmpty {
completionHandler?(.failure(error))
} else {
retrievingContext.appendError(error, to: currentSource)
let finalError = KingfisherError.imageSettingError(
reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors)
)
completionHandler?(.failure(finalError))
}
}
}
}
}
// 然后调用真正用来创建DownloadTask的retrieveImage方法,并将handler作为闭包传进去作为结果处理函数
return retrieveImage(
with: source,
context: retrievingContext)
{
result in
handler(currentSource: source, result: result)
}
}


private func retrieveImage(
with source: Source,
context: RetrievingContext,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?
) -> DownloadTask? {
let options = context.options
// 1. 设置中是否强制刷新,如果是那么强制重载并缓存图片数据
if options.forceRefresh {
return loadAndCacheImage(
source: source,
context: context,
completionHandler: completionHandler)?.value

} else {
// 2. 先从缓存中加载图片
let loadedFromCache = retrieveImageFromCache(
source: source,
context: context,
completionHandler: completionHandler)
// 3. 如果从缓存中找到了对应的图片,则返回nil不生成下载任务,执行结束
if loadedFromCache {
return nil
}
// 4. 如果设置了只能从缓存中加载对应图片,那么输出错误,执行结束
if options.onlyFromCache {
let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
completionHandler?(.failure(error))
return nil
}
// 5. 缓存中没找到,只能重新加载并缓存图片数据
return loadAndCacheImage(
source: source,
context: context,
completionHandler: completionHandler)?.value
}
}

图片缓存查找

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
func retrieveImageFromCache(
source: Source,
context: RetrievingContext,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
{
let options = context.options
// 1. 首先从targetCache中查找目标图片数据.
let targetCache = options.targetCache ?? cache
let key = source.cacheKey
let targetImageCached = targetCache.imageCachedType(
forKey: key, processorIdentifier: options.processor.identifier)
// 判断缓存是否有效:是否能找到source对应的图片数据 且 图片缓存数据的来源符合设置中的要求
// 设置中的fromMemoryCacheOrRefresh表示图片缓存数据应当只来源于内存
// 所以如果该属性为false或者缓存数据来源为内存的情况下来源是合法的
let validCache = targetImageCached.cached &&
(options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory)
// 如果缓存有效则处理缓存图片
if validCache {
targetCache.retrieveImage(forKey: key, options: options) { result in
guard let completionHandler = completionHandler else { return }
options.callbackQueue.execute {
result.match(
onSuccess: { cacheResult in
let value: Result<RetrieveImageResult, KingfisherError>
if let image = cacheResult.image {
value = result.map {
RetrieveImageResult(
image: image,
cacheType: $0.cacheType,
source: source,
originalSource: context.originalSource
)
}
} else {
value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
}
completionHandler(value)
},
onFailure: { _ in
completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
}
)
}
}
return true
}

// 2. targetCache中没有查找到,从originalCache中查找
let originalCache = options.originalCache ?? targetCache
if originalCache === targetCache && options.processor == DefaultImageProcessor.default {
return false
}

// 判断缓存是否有效,跟第一步在targetCache中查找的逻辑基本一致
let originalImageCacheType = originalCache.imageCachedType(
forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier)
let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh
let canUseOriginalImageCache =
(canAcceptDiskCache && originalImageCacheType.cached) ||
(!canAcceptDiskCache && originalImageCacheType == .memory)

// originalCache中查找到有效缓存之后,开始处理缓存中的originalData
if canUseOriginalImageCache {
var optionsWithoutProcessor = options
optionsWithoutProcessor.processor = DefaultImageProcessor.default
originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in
result.match(
onSuccess: { cacheResult in
guard let image = cacheResult.image else {
assertionFailure("The image (under key: \(key) should be existing in the original cache.")
return
}
// 使用processor来处理原始的数据
let processor = options.processor
(options.processingQueue ?? self.processingQueue).execute {
let item = ImageProcessItem.image(image)
guard let processedImage=processor.process(item: item, options: options) else {
let error = KingfisherError.processorError(
reason: .processingFailed(processor: processor, item: item))
options.callbackQueue.execute { completionHandler?(.failure(error)) }
return
}

var cacheOptions = options
cacheOptions.callbackQueue = .untouch

let coordinator = CacheCallbackCoordinator(
shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false)
// 原始数据处理成功之后将处理之后的原始数据放入target缓存中,防止重复处理
targetCache.store(
processedImage,
forKey: key,
options: cacheOptions,
toDisk: !options.cacheMemoryOnly)
{
_ in
coordinator.apply(.cachingImage) {
let value = RetrieveImageResult(
image: processedImage,
cacheType: .none,
source: source,
originalSource: context.originalSource
)
options.callbackQueue.execute { completionHandler?(.success(value)) }
}
}

coordinator.apply(.cacheInitiated) {
let value = RetrieveImageResult(
image: processedImage,
cacheType: .none,
source: source,
originalSource: context.originalSource
)
options.callbackQueue.execute { completionHandler?(.success(value)) }
}
}
},
onFailure: { _ in
// 未在缓存中找到对应的数据,实际上这种情况并不会发生
// 因为我们是在确定了缓存中有对应的内容之后才去缓存中取对应的数据的
options.callbackQueue.execute {
completionHandler?(
.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
)
}
}
)
}
return true
}

return false
}
}

图片加载

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
@discardableResult
func loadAndCacheImage(
source: Source,
context: RetrievingContext,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
{
let options = context.options
// 定义内嵌函数,封装了缓存图片的逻辑
// 在下面的代码中,无论是网络加载图片还是本地加载图片,都会将此内嵌函数作为completionHandler传入
func _cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>) {
cacheImage(
source: source,
options: options,
context: context,
result: result,
completionHandler: completionHandler
)
}

switch source {
// 如果为网络数据,则生成网络下载的task下载网络数据
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
}
// 如果为本地数据,则返回本地加载数据的task
case .provider(let provider):
provideImage(provider: provider, options: options, completionHandler: _cacheImage)
return .dataProviding
}
}

缓存图片

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
// 上面的内嵌函数中调用了另外一个方法cacheImage来实现图片的缓存
private func cacheImage(
source: Source,
options: KingfisherParsedOptionsInfo,
context: RetrievingContext,
result: Result<ImageLoadingResult, KingfisherError>,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?
)
{
switch result {
case .success(let value):
let needToCacheOriginalImage = options.cacheOriginalImage &&
options.processor != DefaultImageProcessor.default
let coordinator = CacheCallbackCoordinator(
shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage)
// Add image to cache.
let targetCache = options.targetCache ?? self.cache
targetCache.store(
value.image,
original: value.originalData,
forKey: source.cacheKey,
options: options,
toDisk: !options.cacheMemoryOnly)
{
_ in
coordinator.apply(.cachingImage) {
let result = RetrieveImageResult(
image: value.image,
cacheType: .none,
source: source,
originalSource: context.originalSource
)
completionHandler?(.success(result))
}
}

// Add original image to cache if necessary.

if needToCacheOriginalImage {
let originalCache = options.originalCache ?? targetCache
originalCache.storeToDisk(
value.originalData,
forKey: source.cacheKey,
processorIdentifier: DefaultImageProcessor.default.identifier,
expiration: options.diskCacheExpiration)
{
_ in
coordinator.apply(.cachingOriginalImage) {
let result = RetrieveImageResult(
image: value.image,
cacheType: .none,
source: source,
originalSource: context.originalSource
)
completionHandler?(.success(result))
}
}
}

coordinator.apply(.cacheInitiated) {
let result = RetrieveImageResult(
image: value.image,
cacheType: .none,
source: source,
originalSource: context.originalSource
)
completionHandler?(.success(result))
}

case .failure(let error):
completionHandler?(.failure(error))
}
}

一些细节:上面的代码中我们经常会用到CacheCallbackCoordinator类,这个类主要负责记录当前缓存的状态,并根据状态之间的改变有条件的触发trigger闭包,其核心代码在下方

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
// 负责更新缓存状态,并且按照当前的状态与将要更新的状态组合来有条件地执行通过参数传入的trigger闭包
func apply(_ action: Action, trigger: () -> Void) {
switch (state, action) {
case (.done, _):
break
// From .idle
case (.idle, .cacheInitiated):
if !shouldWaitForCache {
state = .done
trigger()
}
case (.idle, .cachingImage):
if shouldCacheOriginal {
state = .imageCached
} else {
state = .done
trigger()
}
case (.idle, .cachingOriginalImage):
state = .originalImageCached
// From .imageCached
case (.imageCached, .cachingOriginalImage):
state = .done
trigger()
// From .originalImageCached
case (.originalImageCached, .cachingImage):
state = .done
trigger()
default:
assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)")
}
}

缓存

上面的KingfisherManager的代码当中经常会使用到targetCache与originalCache都是ImageCache的一个单例对象,ImageCache类的内部吃用内存存储与硬盘存储的实例,并且会监听系统相关的状态变化并响应变化。收到UIApplication.didReceiveNotification之后会主动清理内存缓存,收到UIApplication.WillTerminateNotification之后会主动清理已经过期的缓存,收到UIApplication.didEnterBackgrounNotification之后会在后台清理过气的缓存

1
2
3
4
5
6
7
8
9
10
11
// ImageCache类属性的简单代码
open class ImageCache {
// 单例
public static let `default` = ImageCache(name: "default")
// 内存存储对象
public let memoryStorage: MemoryStorage.Backend<KFCrossPlatformImage>
// 硬盘存储对象
public let diskStorage: DiskStorage.Backend<Data>
// 要工作于哪一个队列
private let ioQueue: DispatchQueue
}

在外部,我们通过调用ImageCache的store方法来向缓存中插入图片数据

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
open func store(
_ image: KFCrossPlatformImage,
original: Data? = nil,
forKey key: String,
options: KingfisherParsedOptionsInfo,
toDisk: Bool = true,
completionHandler: ((CacheStoreResult) -> Void)? = nil
){
let identifier = options.processor.identifier
let callbackQueue = options.callbackQueue

let computedKey = key.computedKey(with: identifier)
// 先将图片存储在内存当中
memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration)
// 接着判断是否需要存储到硬盘,如果不需要直接调用回调然后结束流程
guard toDisk else {
if let completionHandler = completionHandler {
let result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(()))
callbackQueue.execute { completionHandler(result) }
}
return
}
// 将图片数据序列化之后存储到硬盘当中
ioQueue.async {
let serializer = options.cacheSerializer
if let data = serializer.data(with: image, original: original) {
self.syncStoreToDisk(
data,
forKey: key,
processorIdentifier: identifier,
callbackQueue: callbackQueue,
expiration: options.diskCacheExpiration,
completionHandler: completionHandler)
} else {
guard let completionHandler = completionHandler else { return }

let diskError = KingfisherError.cacheError(
reason: .cannotSerializeImage(image: image, original: original, serializer: serializer))
let result = CacheStoreResult(
memoryCacheResult: .success(()),
diskCacheResult: .failure(diskError))
callbackQueue.execute { completionHandler(result) }
}
}
}

存储至内存:MemoryStorage实例的storeNoThrow方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func storeNoThrow(
value: T,
forKey key: String,
expiration: StorageExpiration? = nil)
{
// 首先加解锁保证这是一个线程安全的操作
lock.lock()
defer { lock.unlock() }
// 如果在存储是判断图片的缓存已经到期,那么就不再进行存储
let expiration = expiration ?? config.expiration
guard !expiration.isExpired else { return }
// 由键值对生成一个StorageObject
let object = StorageObject(value, key: key, expiration: expiration)
// 将键值对与键存储入自身的storage属性中去,自身的storage属性是一个NSCache对象
storage.setObject(object, forKey: key as NSString, cost: value.cacheCost)
// 同时为了可以快速判断缓存中是否存在某个键的数据,拥有一个keys的set属性用来放置键
keys.insert(key)
}

存储至硬盘:DiskStorage实例的store方法

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
func store(
value: T,
forKey key: String,
expiration: StorageExpiration? = nil) throws
{
// 如果在存储是判断图片的缓存已经到期,那么就不再进行存储
let expiration = expiration ?? config.expiration
guard !expiration.isExpired else { return }
// 参数value接受DataTransformable协议的泛型,下面尝试将value转成data类型的数据
let data: Data
do {
data = try value.toData()
} catch {
throw KingfisherError.cacheError(reason: .cannotConvertToData(object: value, error: error))
}
// 根据key来计算该数据应该存储的位置,并将数据写出对应路径的文件中
let fileURL = cacheFileURL(forKey: key)
do {
try data.write(to: fileURL)
} catch {
throw KingfisherError.cacheError(
reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error)
)
}
// 尝试将文件创建时间以及缓存过期时间信息写入文件的元数据中
let now = Date()
let attributes: [FileAttributeKey : Any] = [
// The last access date.
.creationDate: now.fileAttributeDate,
// The estimated expiration date.
.modificationDate: expiration.estimatedExpirationSinceNow.fileAttributeDate
]
do {
try config.fileManager.setAttributes(attributes, ofItemAtPath: fileURL.path)
} catch {
try? config.fileManager.removeItem(at: fileURL)
throw KingfisherError.cacheError(
reason: .cannotSetCacheFileAttribute(
filePath: fileURL.path,
attributes: attributes,
error: error
)
)
}

maybeCachedCheckingQueue.async {
self.maybeCached?.insert(fileURL.lastPathComponent)
}
}

相关通知的监听

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
let notifications = [
// 收到内存警告的时候,清除所有的内存缓存
(UIApplication.didReceiveMemoryWarningNotification, #selector(clearMemoryCache)),
// APP将要Terminate的时候,清除所有的硬盘缓存
(UIApplication.willTerminateNotification, #selector(cleanExpiredDiskCache)),
// 当APP进入后台时也清理硬盘缓存
(UIApplication.didEnterBackgroundNotification, #selector(backgroundCleanExpiredDiskCache))
]
notifications.forEach {
NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil)
}
// 清理所有内存缓存
@objc public func clearMemoryCache() {
try? memoryStorage.removeAll()
}
// 清理过期硬盘缓存
@objc func cleanExpiredDiskCache() {
cleanExpiredDiskCache(completion: nil)
}
// APP进入后台
@objc public func backgroundCleanExpiredDiskCache() {
guard let sharedApplication = KingfisherWrapper<UIApplication>.shared else { return }
// 封装结束后台任务的逻辑
func endBackgroundTask(_ task: inout UIBackgroundTaskIdentifier) {
sharedApplication.endBackgroundTask(task)
#if swift(>=4.2)
task = UIBackgroundTaskIdentifier.invalid
#else
task = UIBackgroundTaskInvalid
#endif
}
// 创建后台任务
var backgroundTask: UIBackgroundTaskIdentifier!
backgroundTask = sharedApplication.beginBackgroundTask {
endBackgroundTask(&backgroundTask!)
}
// 调用cleanExpiredDiskCache清理过期硬盘缓存,完成之后也销毁后台任务
cleanExpiredDiskCache {
endBackgroundTask(&backgroundTask!)
}
}

缓存移除

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
open func removeImage(forKey key: String,
processorIdentifier identifier: String = "",
fromMemory: Bool = true,
fromDisk: Bool = true,
callbackQueue: CallbackQueue = .untouch,
completionHandler: (() -> Void)? = nil)
{
let computedKey = key.computedKey(with: identifier)
// 如果标注了要从内存中删除,就调用内存缓存实例memoryStorage的remove方法来删除缓存
if fromMemory {
try? memoryStorage.remove(forKey: computedKey)
}
// 如果标注了要从硬盘中删除,就调用硬盘缓存实例diskStorage的remove方法来删除缓存
if fromDisk {
ioQueue.async{
try? self.diskStorage.remove(forKey: computedKey)
if let completionHandler = completionHandler {
callbackQueue.execute { completionHandler() }
}
}
} else {
if let completionHandler = completionHandler {
callbackQueue.execute { completionHandler() }
}
}
}
// 内存缓存实例memoryStorage的remove方法:从NSCache中移除对应Key的数据,并删除Key
func remove(forKey key: String) throws {
lock.lock()
defer { lock.unlock() }
storage.removeObject(forKey: key as NSString)
keys.remove(key)
}
// 硬盘缓存实例diskStorage的remove方法:使用FileManager删除特定文件
func removeFile(at url: URL) throws {
try config.fileManager.removeItem(at: url)
}

网络加载

如果缓存中没有查找到对应的图片数据,则会生成一个DownloadTask来下载图片的数据。从下面的代码中可以看出来DownloadTask只是对SessionDataTask的简单封装,核心的图片下载逻辑室友SessionDataTask完成的。

1
2
3
4
5
6
7
8
9
public struct DownloadTask {
public let sessionTask: SessionDataTask

public let cancelToken: SessionDataTask.CancelToken

public func cancel() {
sessionTask.cancel(token: cancelToken)
}
}

SessionDataTask

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
public class SessionDataTask {

public typealias CancelToken = Int

struct TaskCallback {
let onCompleted: Delegate<Result<ImageLoadingResult, KingfisherError>, Void>?
let options: KingfisherParsedOptionsInfo
}

/// 当前任务下载到的数据
public private(set) var mutableData: Data
// 底层的原生URLSessionDataTask
public let task: URLSessionDataTask
// 回调容器
private var callbacksStore = [CancelToken: TaskCallback]()
var callbacks: [SessionDataTask.TaskCallback] {
lock.lock()
defer { lock.unlock() }
return Array(callbacksStore.values)
}
// 记录当前的Token,SessionDataTask通过自增Token的方式给每一个回调都分配一个唯一的Token值
private var currentToken = 0
private let lock = NSLock()

let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>()
let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>()
// 标识SessionDataTask是否正在运行
var started = false
var containsCallbacks: Bool {
return !callbacks.isEmpty
}

init(task: URLSessionDataTask) {
self.task = task
mutableData = Data()
}
// 向SessionDataTask实例的回调仓库中添加一个回调
// 添加一个回调会返回该回调对应的一个CancelToken
func addCallback(_ callback: TaskCallback) -> CancelToken {
lock.lock()
defer { lock.unlock() }
callbacksStore[currentToken] = callback
defer { currentToken += 1 }
return currentToken
}
// 移除特定CancelToken对应的回调
func removeCallback(_ token: CancelToken) -> TaskCallback? {
lock.lock()
defer { lock.unlock() }
if let callback = callbacksStore[token] {
callbacksStore[token] = nil
return callback
}
return nil
}
// 开始请求:将started状态标识设置为true并调用底层原生的URLSessionDataTask实例的resume方法来开启网络请求
func resume() {
guard !started else { return }
started = true
task.resume()
}
// 根据CancelToken关闭(清除)对应的回调,如果关闭之后SessionDataTask的回调数为空
// 则彻底关掉底层的URLSessionDataTask网络任务
func cancel(token: CancelToken) {
guard let callback = removeCallback(token) else {
return
}
if callbacksStore.count == 0 {
task.cancel()
}
onCallbackCancelled.call((token, callback))
}
// 清除所有的回调,并关闭底层的URLSessionDataTask
func forceCancel() {
for token in callbacksStore.keys {
cancel(token: token)
}
}
// SessionDataTask接收到了先传递进来的数据,更新自己存储的数据
func didReceiveData(_ data: Data) {
mutableData.append(data)
}
}

SessionDataTask是对原生URLSessionDataTask的简单封装,初始化时需要传入一个已经创建好的URLSessionDataTask,而URLSessionDataTask是由URLSession生成的,ImageDownloader就是负责这个的:管理全局的URLSession,并创建URLSessionDataTask,最后再创建出SessionDataTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ImageDownloader的初始化方法
public init(name: String) {
if name.isEmpty {
fatalError("[Kingfisher] You should specify a name for the downloader. "
+ "A downloader with empty name is not permitted.")
}

self.name = name
// 初始化了SessionDelegate和URLSession
sessionDelegate = SessionDelegate()
session = URLSession(
configuration: sessionConfiguration,
delegate: sessionDelegate,
delegateQueue: nil)

authenticationChallengeResponder = self
setupSessionHandler()
}

ImageDownloader通过downloadImage方法来创建SessionDataTask

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 在开始进一步的分析之前,我们先来看一个KF中的工具类Delegate
// Delegate是对代理模式的一个简单封装,我们不再需要像传统代理模式那样定义协议,实现协议,将特定类作为代理传递给某个需要他的类
class Delegate<Input, Output> {
init() {}
// 存储一个block
private var block: ((Input) -> Output?)?
// 添加代理逻辑,在传统的代理协议开发模式下相当于给某个类实现代理协议,并将该类的某个对象传递给需要用到这个代理的地方
func delegate<T: AnyObject>(on target: T, block: ((T, Input) -> Output)?) {
// The `target` is weak inside block, so you do not need to worry about it in the caller side.
self.block = { [weak target] input in
guard let target = target else { return nil }
return block?(target, input)
}
}
// 触发代理方法,相当于传统的代理开发模式下代理的持有者调用代理的方法
func call(_ input: Input) -> Output? {
return block?(input)
}
}
// ImageDownloader中的sessionConfiguration默认为.ephemeral(非持久化,无痕)
open var sessionConfiguration = URLSessionConfiguration.ephemeral {
didSet {
session.invalidateAndCancel()
session = URLSession(
configuration: sessionConfiguration,
delegate:sessionDelegate,
delegateQueue: nil
)
}
}
@discardableResult
open func downloadImage(
with url: URL,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
{
// 创建默认的请求
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
request.httpShouldUsePipelining = requestsUsePipelining
// 如果参数中给了RequestModifier,那么首先对创建的请求应用这个Modifier
if let requestModifier = options.requestModifier {
guard let r = requestModifier.modified(for: request) else {
options.callbackQueue.execute {
completionHandler?(.failure(KingfisherError.requestError(reason: .emptyRequest)))
}
return nil
}
request = r
}
// 在对request应用了Modifier之后对request的url进行判空,防止在Modifier中将request的url设置为了nil
guard let url = request.url, !url.absoluteString.isEmpty else {
options.callbackQueue.execute {
completionHandler?(.failure(KingfisherError.requestError(reason: .invalidURL(request: request))))
}
return nil
}

// 将completionHandler包装成Delegate对象
let onCompleted = completionHandler.map {
block -> Delegate<Result<ImageLoadingResult, KingfisherError>, Void> in
let delegate = Delegate<Result<ImageLoadingResult, KingfisherError>, Void>()
delegate.delegate(on: self) { (_, callback) in
block(callback)
}
return delegate
}

// 再把Delegate对象包装为TaskCallback对象
let callback = SessionDataTask.TaskCallback(
onCompleted: onCompleted,
options: options
)

// 如果已经存在一个相同URL的下载任务,那么就只是取出这个任务,向该任务中新添加一个TaskCallback
// 如果不存在,那么久重新创建一个,并将重新创建的downloadTask添加到sessionDelegate对象中
let downloadTask: DownloadTask
if let existingTask = sessionDelegate.task(for: url) {
downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback)
} else {
let sessionDataTask = session.dataTask(with: request)
sessionDataTask.priority = options.downloadPriority
downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback)
}
// 如果对应URL的SessionDataTask先前就存在,并且上一次的请求还未结束,那么就不需要做任何操作
// 直接返回downloadTask即可,因为等到此次未结束的请求有数据更新的时候会直接调用我们之前添加进去的TaskCallback
// 如果对应URL的SessionDataTask并没有正在请求的状态,那么我们需要手动resume该task
// 但在resume之前我们需要为这一次的请求添加一个请求完成的回调
if !sessionTask.started {
sessionTask.onTaskDone.delegate(on: self) { (self, done) in
// Underlying downloading finishes.
// result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback]
let (result, callbacks) = done

// 请求完成之后,分析结果是正确(URLResponse)还是错误(Error),并将结果通知出去
// delegate是一个遵循ImageDownloaderDelegate代理协议的代理属性
do {
let value = try result.get()
self.delegate?.imageDownloader(
self,
didFinishDownloadingImageForURL: url,
with: value.1,
error: nil
)
} catch {
self.delegate?.imageDownloader(
self,
didFinishDownloadingImageForURL: url,
with: nil,
error: error
)
}
// 另外,除了在请求结束收到响应的时候将结果通知出去之外
// 如果请求成功了,那么会开启一个数据转换任务,尝试将响应中的数据转换为一个图片数据
switch result {
case .success(let (data, response)):
// 创建图片数据处理器
let processor = ImageDataProcessor(
data: data,
callbacks: callbacks
processingQueue: options.processingQueue
)
// 给处理器添加回调,在图片数据处理完成之后将结果通知给代理属性和SessionDataTask中的回调
processor.onImageProcessed.delegate(on: self) { (self, result) in
let (result, callback) = result
if let image = try? result.get() {
self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response)
}

let imageResult = result.map {
ImageLoadingResult(image: $0, url: url, originalData: data)
}
let queue = callback.options.callbackQueue
queue.execute { callback.onCompleted?.call(imageResult) }
}
// 开始处理图片数据
processor.process()
// 如果请求失败,则把错误通知给SessionDataTask中的回调
case .failure(let error):
callbacks.forEach { callback in
let queue = callback.options.callbackQueue
queue.execute { callback.onCompleted?.call(.failure(error)) }
}
}
}
// 通知代理属性,图片下载任务即将开始
delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request)
// 调用resume方法开始下载图片
sessionTask.resume()
}
return downloadTask
}

SessionDelegate类作为URLSession的代理,同时也负责管理我们创建的DownloadTask。SessionDelegate中有一个字典属性,该字典以下载任务的URL为key,DownloadTask为value。在上面我们分析的downloadImage用来创建图片下载任务的方法中可以看出来这个字典的作用。每次创建一个DownloadTask就把这个task放入SessionDelegate的字典中存储,等到下次需要使用到同一张图片的时候就不用重复创建下载任务。并且如果这个下载任务还未结束就又遇到了同一个URL的下载任务,甚至可以不用重复请求,直接使用上一次的请求结果。

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
// add方法会重新创建一个SessionDataTask并存入字典中
func add(
_ dataTask: URLSessionDataTask,
url: URL,
callback: SessionDataTask.TaskCallback) -> DownloadTask
{
// 加锁
lock.lock()
defer { lock.unlock() }
// 由原生的URLSessionDataTask创建一个SessionDataTask包装
let task = SessionDataTask(task: dataTask)
// 在这里为SessionDataTask添加某项CallbackTask被关闭时的回调
task.onCallbackCancelled.delegate(on: self) { [weak task] (self, value) in
guard let task = task else { return }
let (token, callback) = value
// 当该回调被关闭的时候,通知该回调任务因为提前被主动关闭而失败
let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token))
task.onTaskDone.call((.failure(error), [callback]))
// 如果某个回调被关闭之后,SessionDataTask中没有其他的回调Task了,那么该SessionDataTask就没有用处了
// 从SessionDelegate的字典中移除
if !task.containsCallbacks {
let dataTask = task.task
self.remove(dataTask)
}
}
// 将刚刚创建的SessionDataTask加入到SessionDelegate中的字典进行管理
let token = task.addCallback(callback)
tasks[url] = task
return DownloadTask(sessionTask: task, cancelToken: token)
}
// append方法会将回调闭包的task添加到一个字典中已经存在的SessionDataTask当中
func append(
_ task: SessionDataTask,
url: URL,
callback: SessionDataTask.TaskCallback) -> DownloadTask
{
let token = task.addCallback(callback)
return DownloadTask(sessionTask: task, cancelToken: token)
}
// 将某一个URLSessionTask对应的SessionTask从字典中移除
private func remove(_ task: URLSessionTask) {
guard let url = task.originalRequest?.url else {
return
}
lock.lock()
defer {lock.unlock()}
tasks[url] = nil
}
--- 本文结束 The End ---