Kingfisher源码分析

微信扫一扫,分享到朋友圈

Kingfisher源码分析

一:普通姿势

// 普通姿势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.简单使用

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

2. kf 定义

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

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
}
}

3.1 setImage(with: resource…)

@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? ,我们来看下这两者的区别:

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 协议

extension URL: Resource {
public var cacheKey: String { return absoluteString }
public var downloadURL: URL { return self }
}

3.2 setImage(with: source…)

@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
}

3.3 KingfisherManager.shared.retrieveImage(with: source…)

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
}
}

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

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
}
}

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

@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
}
}

3.5.1.1 创建 DownloadTask: downloadImage(with url…)

@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 对应的多个回调

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

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))
}

3.5.1.2 Data 转换为 Image: process(item: ImageProcessItem…)

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

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

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
}

3.5.2 缓存图片 store(_ image: Image…)

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) }
}
}
}

3.6 完整流程

三:补充

1. 缓存策略

  1. 内存缓存:

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

  2. 磁盘缓存:

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

2. 其它细节

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

    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)
    }

NASA宣布独立审查委员会对“火星样本返回”任务的评估结果

上一篇

深入Python中的 Collections 模块

下一篇

你也可能喜欢

Kingfisher源码分析

长按储存图像,分享给朋友