October 12, 2018
Kingfisher
通过给 ImageView 和 Button 分别扩展了两个分类方法来设置图片
这里没有使用 UIImageView 和 UIButton 是因为该库也支持 MacOS ,所以在 MacOS 上是 NSImageView 和 NSButton 控件。详情见 Kingfisher.swift 中
// 判断系统软件版本是 MacOS 还是 其他平台来分别导入对应 framework
#if os(macOS)
import AppKit
public typealias Image = NSImage
public typealias View = NSView
public typealias Color = NSColor
public typealias ImageView = NSImageView
public typealias Button = NSButton
#else
import UIKit
public typealias Image = UIImage
public typealias Color = UIColor
#if !os(watchOS)
public typealias ImageView = UIImageView
public typealias View = UIView
public typealias Button = UIButton
#else
import WatchKit
#endif
#endif
设置图片的核心方法
@discardableResult
public func setImage(with resource: Resource?,
placeholder: Placeholder? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
- Resource 图片的 URL
- placeholder 占位图
- KingfisherOptionsInfo 一个数组,保存图片下载后的一系列处理操作
- progressBlock 进度回调
- completionHandler 完成回调
- 返回一个下载处理 task
方法的核心逻辑
- 首先判断 URL 是否存在,否则直接返回;
- 将用户传的 KingfisherOptionsInfo 和 默认的 KingfisherEmptyOptionsInfo 合并,有过有展位图保存展位图;
- 如果有加载 loading 则开始展示加载 loading 动画;
- 保存下载链接;
- 使用 Kingfisher 的
retrieveImage(with:options:progressBlock:completionHandler)
方法去获取图片; - retrieveImage 方法返回一个 图片处理 task 保存并返回这个task
Kingfisher 利用 runtime 给 ImageView 分别扩展了 webURL 和 imageTask 属性,用来分别保存当前控件上的图片链接和下载任务,方便以后取回复用取回图片和操作这个下载任务。
UIButton 和 NSButton 因为存在背景图片所以增加了一个方法
@discardableResult
public func setBackgroundImage(with resource: Resource?,
for state: UIControl.State,
placeholder: UIImage? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
方法的实现过程思路和 UIImageView 一样。同样也给 Button 扩展了 背景图 url 和 下载任务 task
扩展里给 watchOS 增加了 WKInterfaceImage
方法,该控件与 iOS 的 UIImageView 一样,所以里面的实现基本和 ImageView 的扩展实现差不多。
那我们就顺着代码的实现看看这个 retrieveImage
方法到底做了什么
图片的取回操作,在最核心的 KingfisherManager.swift
文件中,一共有 3 个 核心方法
retrieveImage(with resource:options:progressBlock:completionHandler:) -> RetrieveImageTask
downloadAndCacheImage(with url:forKey key:retrieveImageTask:progressBlock: completionHandler:options:) -> RetrieveImageDownloadTask?
tryToRetrieveImageFromCache(forKey key: with url:retrieveImageTask:progressBlock:completionHandler:options:)
其中第一个方法会分别调用下面两个方法,判断 options 中 是否有 forceRefresh
值,如果有则去调用第二方法去下载图片,否则调用第三个方法尝试从缓存中取回图片
下载图片时,其实是调用 ImageDownloader.swift
中的 downloadImage(with:retrieveImageTask:options:progressBlock:completionHandler)
方法
let targetCache = options.targetCache ?? self.cache
if let error = error, error.code == KingfisherError.notModified.rawValue {
// 没有修改。尝试从缓存中找
targetCache.retrieveImage(forKey: key, options: options, completionHandler: {
(cacheImage, cacheType) -> Void in
completionHandler?(cacheImage, nil, cacheType, url)
})
return
}
if let image = image, let originalData = originalData {
// 目标缓存路径缓存图片。
// 如果没有特别要求,则会在磁盘也缓存一份
targetCache.store(image,
original: originalData,
forKey: key,
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 ?? targetCache
let defaultProcessor = DefaultImageProcessor.default
processQueue.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 {
completionHandler?(image, error, .none, url)
}
取回图片的逻辑也很简单
// If found, we could finish now.
if image != nil {
diskTaskCompletionHandler(image, nil, cacheType, url)
return
}
// If not found, and we are using a default processor, download it!
let processor = options.processor
guard processor != DefaultImageProcessor.default else {
handleNoCache()
return
}
// If processor is not the default one, we have a chance to check whether
// the original image is already in cache.
let originalCache = options.originalCache ?? targetCache
let optionsWithoutProcessor = options.removeAllMatchesIgnoringAssociatedValue(.processor(processor))
originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { image, cacheType in
// If we found the original image, there is no need to download it again.
// We could just apply processor to it now.
guard let image = image else {
handleNoCache()
return
}
processQueue.async {
guard let processedImage = processor.process(item: .image(image), options: options) else {
options.callbackDispatchQueue.safeAsync {
diskTaskCompletionHandler(nil, nil, .none, url)
}
return
}
targetCache.store(processedImage,
original: nil,
forKey: key,
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)
options.callbackDispatchQueue.safeAsync {
diskTaskCompletionHandler(processedImage, nil, cacheType, url)
}
})
if options.waitForCache == false {
options.callbackDispatchQueue.safeAsync {
diskTaskCompletionHandler(processedImage, nil, .none, url)
}
}
}
}
- 首先尝试存目标缓存中取,如果取到了则直接返回;
- 如果 options 的 processor 不是默认的,则直接重新下载;
- 尝试从原始缓存中找,如果没找到则重新下载;
- 加工图片,并在目标缓存中在存储一份,回调成功闭包