Kingfisher源码分析

普通姿势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 普通姿势1
func originMethod1() {
guard let url = URL(string: imageURL),
let data = try? Data(contentsOf: url) else { return }
imageView.image = UIImage(data: data)
}

// 普通姿势2
func originMethod2() {
DispatchQueue.global().async {
guard let url = URL(string: self.imageURL),
let data = try? Data(contentsOf: url) else { return }
DispatchQueue.main.async {
self.imageView.image = UIImage(data: data)
}
}
}

上面是两种普通设置网络图片的方法,弊端也是很明显的,originMethod1 会阻塞主线程,并且它们都没办法中途取消任务。

Kingfisher

以 Kingfisher v5.6.0 版本代码为示例

简单使用

1
2
let url = URL(string: imageURL)
imageView.kf.setImage(with: url)

kf 定义

先看 kf 的定义,返回一个包含自己的 KingfisherWrapper 对象,可以调用 Setting Image 一系列函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension ImageView: KingfisherCompatible { }

extension KingfisherCompatible {
/// Gets a namespace holder for Kingfisher compatible types.
public var kf: KingfisherWrapper<Self> {
get { return KingfisherWrapper(self) }
set { }
}
}

public struct KingfisherWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}

setImage(with: resource…)

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.map { .network($0) },
placeholder: placeholder,
options: options,
progressBlock: progressBlock,
completionHandler: completionHandler)
}

@discardableResult:表示取消不使用返回值的警告

可以看到 setImage(with: url) 内部调用了另外一个相似函数,只不过 source 参数类型从 Resource? 变成 Source? ,我们来看下这两者的区别:

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
public protocol Resource {
var cacheKey: String { get }
var downloadURL: URL { get }
}

public enum Source {
public enum Identifier {
public typealias Value = UInt
static var current: Value = 0
static func next() -> Value {
current += 1
return current
}
}

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(_): return nil
}
}
}

Resource 是协议,Source 是枚举,Source 有两种类型:.network(Resource) 和 .provider(ImageDataProvider)

imageView.kf.setImage(with: url) 可以直接传入 url,是因为 URL 实现了 Resource 协议

1
2
3
4
extension URL: Resource {
public var cacheKey: String { return absoluteString }
public var downloadURL: URL { return self }
}

setImage(with: source…)

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
@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 为 nil,设置 imageView 的 image 为 placeholder,然后 return
guard let source = source else {
mutatingSelf.placeholder = placeholder
mutatingSelf.taskIdentifier = nil
completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource)))
return nil
}

// KingfisherParsedOptionsInfo: 图片下载的解析选项
// 具体参数含义参考 KingfisherOptionsInfo.swift 文件里 KingfisherOptionsInfoItem 定义
var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions + (options ?? .empty))
let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nil

if !options.keepCurrentImageWhileLoading || noImageOrPlaceholderSet {
// imageView 当前没有 image/placeholder 时,优先设置 placeholder
mutatingSelf.placeholder = placeholder
}

// 图片下载指示器,默认为 nil
let maybeIndicator = indicator
maybeIndicator?.startAnimatingView()

// 设置 taskIdentifier,从1开始,1,2,3...
let issuedIdentifier = Source.Identifier.next()
mutatingSelf.taskIdentifier = issuedIdentifier

// 预加载所有动画图像数据
if base.shouldPreloadAllAnimation() {
options.preloadAllAnimationData = true
}

// 下载进度 block
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 }
}

// 获取图片
let task = KingfisherManager.shared.retrieveImage(
with: source,
options: options,
completionHandler: { result in
// 处理结果
CallbackQueue.mainCurrentOrAsync.execute {
maybeIndicator?.stopAnimatingView()
// 判断 Identifier 是否相等
// 用来避免 UITableviewCell,UICollectionViewCell 重用时数据显示错误
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)
}
// Identifier 不相等,返回错误原因
let error = KingfisherError.imageSettingError(reason: reason)
completionHandler?(.failure(error))
return
}

// 置空 imageTask,taskIdentifier
mutatingSelf.imageTask = nil
mutatingSelf.taskIdentifier = nil

switch result {
case .success(let value):
// 判断 直接/过渡动画 设置 image
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:
// 失败情况下,设置 imageView.image = options.onFailureImage
if let image = options.onFailureImage {
self.base.image = image
}
completionHandler?(result)
}
}
}
)

// 设置 imageTask,用于做 cancelDownloadTask() 操作
mutatingSelf.imageTask = task
return task
}

KingfisherManager.shared.retrieveImage(with: source…)

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
func retrieveImage(
with source: Source,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
{
// 忽略缓存,直接下载并缓存图片
if options.forceRefresh {
return loadAndCacheImage(
source: source,
options: options,
completionHandler: completionHandler)?.value

} else {
// 从缓存中获取图片
let loadedFromCache = retrieveImageFromCache(
source: source,
options: options,
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,
options: options,
completionHandler: completionHandler)?.value
}
}

从缓存中获取图片: retrieveImageFromCache(source: Source…)

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
func retrieveImageFromCache(
source: Source,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
{
// 1. Check whether the image was already in target cache. If so, just get it.
// 1.检查内存缓存,如果有缓存,就返回图片
let targetCache = options.targetCache ?? cache
let key = source.cacheKey
let targetImageCached = targetCache.imageCachedType(
forKey: key, processorIdentifier: options.processor.identifier)

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)
}
} else {
value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
}
completionHandler(value)
},
onFailure: { _ in
completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
}
)
}
}
return true
}

// 2. Check whether the original image exists. If so, get it, process it, save to storage and return.
// 2.检查磁盘缓存,如果有缓存,返回图片,并存储到内存缓存
let originalCache = options.originalCache ?? targetCache
// No need to store the same file in the same cache again.
if originalCache === targetCache && options.processor == DefaultImageProcessor.default {
return false
}

// Check whether the unprocessed image existing or not.
let originalImageCached = originalCache.imageCachedType(
forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier).cached
if originalImageCached {
// Now we are ready to get found the original image from cache. We need the unprocessed image, so remove
// any processor from options first.
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 {
return
}

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
targetCache.store(
processedImage,
forKey: key,
options: cacheOptions,
toDisk: !options.cacheMemoryOnly)
{
_ in
if options.waitForCache {
let value = RetrieveImageResult(image: processedImage, cacheType: .none, source: source)
options.callbackQueue.execute { completionHandler?(.success(value)) }
}
}

if !options.waitForCache {
let value = RetrieveImageResult(image: processedImage, cacheType: .none, source: source)
options.callbackQueue.execute { completionHandler?(.success(value)) }
}
}
},
onFailure: { _ in
// This should not happen actually, since we already confirmed `originalImageCached` is `true`.
// Just in case...
options.callbackQueue.execute {
completionHandler?(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
}
}
)
}
return true
}

return false
}
}

下载并缓存图片: loadAndCacheImage(source…)

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
@discardableResult
func loadAndCacheImage(
source: Source,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
{
// 添加图片到缓存
func cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>)
{
switch result {
case .success(let value):
// 默认缓存是 ImageCache.default = ImageCache(name: "default")
let targetCache = options.targetCache ?? self.cache
targetCache.store(
value.image,
original: value.originalData,
forKey: source.cacheKey,
options: options,
toDisk: !options.cacheMemoryOnly)
{
_ in
// 缓存成功后再执行 completionHandler 回调
if options.waitForCache {
let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
completionHandler?(.success(result))
}
}

let needToCacheOriginalImage = options.cacheOriginalImage &&
options.processor != DefaultImageProcessor.default
// 缓存图片的原始数据 Data
if needToCacheOriginalImage {
let originalCache = options.originalCache ?? targetCache
originalCache.storeToDisk(
value.originalData,
forKey: source.cacheKey,
processorIdentifier: DefaultImageProcessor.default.identifier,
expiration: options.diskCacheExpiration)
}

// 直接执行 completionHandler 回调
if !options.waitForCache {
let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
completionHandler?(.success(result))
}
case .failure(let error):
completionHandler?(.failure(error))
}
}

switch source {
case .network(let resource):
let downloader = options.downloader ?? self.downloader

// 创建 DownloadTask,下载图片
guard let task = downloader.downloadImage(
with: resource.downloadURL,
options: options,
completionHandler: cacheImage) else {
return nil
}
return .download(task)

case .provider(let provider):
provideImage(provider: provider, options: options, completionHandler: cacheImage)
return .dataProviding
}
}
创建 DownloadTask: downloadImage(with 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
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
@discardableResult
func downloadImage(
with url: URL,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
{
// 创建 request,timeoutInterval 默认为 15s
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
request.httpShouldUsePipelining = requestsUsePipelining

// 修改 request
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
}

// 判断 url 是否为 nil / empty
guard let url = request.url, !url.absoluteString.isEmpty else {
options.callbackQueue.execute {
completionHandler?(.failure(KingfisherError.requestError(reason: .invalidURL(request: request))))
}
return nil
}

// Wraps `completionHandler` to `onCompleted` respectively.
// 将 completionHandler 包装成 onCompleted
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
}

// SessionDataTask.TaskCallback is a wrapper for `onCompleted` and `options` (for processor info)
// SessionDataTask.TaskCallback 是 onCompleted 和 options 的包装(用于处理下载完成后的图片 Data)
let callback = SessionDataTask.TaskCallback(
onCompleted: onCompleted,
options: options
)

// Ready to start download. Add it to session task manager (`sessionHandler`)

// 如果 sessionDelegate 里有相同 url 的 task 时,就不创建新的 SessionDataTask,仅把 callback 添加到对应 SessionDataTask 的 callbacksStore 里即可,请求结束后,顺序依次处理 callbacks,避免重复请求,浪费用户流量
let downloadTask: DownloadTask
if let existingTask = sessionDelegate.task(for: url) {
downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback)
} else {
// 创建新的 URLSessionDataTask
let sessionDataTask = session.dataTask(with: request)
sessionDataTask.priority = options.downloadPriority
downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback)
}

let sessionTask = downloadTask.sessionTask

// 当下载完成后执行 sessionTask 的 onTaskDone 回调
if !sessionTask.started {
sessionTask.onTaskDone.delegate(on: self) { (self, done) in
// Underlying downloading finishes.
// result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback]
let (result, callbacks) = done

// Before processing the downloaded data.
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 {
// Download finished. Now process the data to an image.
// 下载完成,将数据转化成图片
case .success(let (data, response)):
let processor = ImageDataProcessor(
data: data, callbacks: callbacks, processingQueue: options.processingQueue)
processor.onImageProcessed.delegate(on: self) { (self, result) in
// `onImageProcessed` will be called for `callbacks.count` times, with each
// `SessionDataTask.TaskCallback` as the input parameter.
// result: Result<Image>, callback: SessionDataTask.TaskCallback
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()

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)

// 开始下载
sessionTask.resume()
}
return downloadTask
}

下载这步看着有点绕,我们来梳理一下:

  1. ImageDownloader 是下载管理器,这个类中包含 session、sessionDelegate 等
  2. SessionDelegate 是 URLSession 的 delegate,这个类中包含 tasks, 类型是 [URL: SessionDataTask],以及实现 URLSessionDataDelegate 相关协议
  3. SessionDataTask 是 URLSessionDataTask 的封装,里面包含了 task、callbacksStore,callbacksStore 是 task 对应的多个回调

1161604912699_.pic_hd.png

当 sessionTask.resume() 开始下载时,SessionDelegate 就开始接受数据,接受完数据后,会调用 urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 协议,然后再调用 onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) 方法,找到对应的 sessionTask,执行 sessionTask 的 onTaskDone 回调,传入 result 和 callbacks。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let sessionTask = self.task(for: task) else { return }

...
onCompleted(task: task, result: result)
}

private func onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) {
guard let sessionTask = self.task(for: task) else {
return
}
remove(task)
sessionTask.onTaskDone.call((result, sessionTask.callbacks))
}
Data 转换为 Image: process(item: ImageProcessItem…)

可以看到是通过 Image(data: data, scale: options.scale) 方法生成对应图片

通过 Data 前面的 bytes 判断图片对应的格式

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
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> Image? {
switch item {
case .image(let image):
return image.kf.scaled(to: options.scaleFactor)
case .data(let data):
return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions)
}
}

public static func image(data: Data, options: ImageCreatingOptions) -> Image? {
var image: Image?
switch data.kf.imageFormat {
case .JPEG:
image = Image(data: data, scale: options.scale)
case .PNG:
image = Image(data: data, scale: options.scale)
case .GIF:
image = KingfisherWrapper.animatedImage(data: data, options: options)
case .unknown:
image = Image(data: data, scale: options.scale)
}
return image
}

public var imageFormat: ImageFormat {
guard base.count > 8 else { return .unknown }

var buffer = [UInt8](repeating: 0, count: 8)
base.copyBytes(to: &buffer, count: 8)

if buffer == ImageFormat.HeaderData.PNG {
return .PNG

} else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0],
buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1],
buffer[2] == ImageFormat.HeaderData.JPEG_IF[0]
{
return .JPEG

} else if buffer[0] == ImageFormat.HeaderData.GIF[0],
buffer[1] == ImageFormat.HeaderData.GIF[1],
buffer[2] == ImageFormat.HeaderData.GIF[2]
{
return .GIF
}

return .unknown
}
缓存图片 store(_ image: Image…)
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
open func store(_ image: Image,
original: Data? = nil,
forKey key: String,
options: KingfisherParsedOptionsInfo,
toDisk: Bool = true,
completionHandler: ((CacheStoreResult) -> Void)? = nil)
{
// 处理器的标示符,默认为 ""
let identifier = options.processor.identifier
let callbackQueue = options.callbackQueue
// 计算缓存 key,默认为 URL 的 absoluteString
let computedKey = key.computedKey(with: identifier)
// Memory storage should not throw.
// 存储图片到内存缓存(NSCache)
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
}

// 存储图片到磁盘(File)
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) }
}
}
}

完整流程

1161604912699_.pic_hd.png

补充

缓存策略

  1. 内存缓存:

    MemoryStorage.Backend 类中有一个 cleanTimer 定时器,默认时间是 120s 去清理一次过期图片,图片的默认过期时间是 300s。如果从缓存中取到了图片,就重置该图片的开始时间,默认过期时间依然是 300s。

  2. 磁盘缓存:

    图片的默认过期时间是 7天,如果从磁盘中取到了图片,就重置该图片的开始时间,默认过期时间依然是 7天。

其它细节

  1. ImageCache 类中添加了一系列的监听通知,比如 App 收到内存警告时,所以当 App 收到内存警告时,不需要再次处理图片缓存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    notifications = [
    (UIApplication.didReceiveMemoryWarningNotification, #selector(clearMemoryCache)),
    (UIApplication.willTerminateNotification, #selector(cleanExpiredDiskCache)),
    (UIApplication.didEnterBackgroundNotification, #selector(backgroundCleanExpiredDiskCache))
    ]

    notifications.forEach {
    NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil)
    }

相关资料

  1. Kingfisher
  2. 测试 Demo