Kingfisher图片下载流程
图片下载流程
imageView.kf.setImage的主要流程
- 将输入参数Resource类型转换内部参数Source类型
- 外部传入的是
URL
,为什么没有直接传入Resource
类型的参数,因为Resource
是一个协议,URL实现了Resource
协议
- 组装解析参数(ParsedOptions)
- 判断是否设置站位图
- 判断是否有加载UI
- 生成标志符号
- 绑定标识符
- 判断是否显示预加载动画
- 设置进度回调,如果有的话
- 判断是否设置进度显示的UI
- 获取图片,监听回调
- 为当前控件绑定下载任务
- 监听回调
- 停止加载动画,如果有的话
- 判断当前任务id与回调的任务Id是否是同一个, 如果不是就是不当前控件的图片
- 清除当前任务
- 清除当前任务Id
-
加载成功:是否有设置转场动画, 如果没有直接设置图片
- 开始转场动画,并设置图片
-
加载失败,判断是否有设置失败的图片,如果有设置,则显示失败站位图
- setImage
func setImage(
with source: Source?,
placeholder: Placeholder? = nil,
parsedOptions: KingfisherParsedOptionsInfo,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
{
var mutatingSelf = self
guard let source = source else {
mutatingSelf.placeholder = placeholder
mutatingSelf.taskIdentifier = nil
completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource)))
return nil
}
var options = parsedOptions
// 判断是否设置站位图
let isEmptyImage = base.image == nil && self.placeholder == nil
if !options.keepCurrentImageWhileLoading || isEmptyImage {
// Always set placeholder while there is no image/placeholder yet.
mutatingSelf.placeholder = placeholder
}
// 判断是否有加载UI
let maybeIndicator = indicator
maybeIndicator?.startAnimatingView()
// 生成标志符号
let issuedIdentifier = Source.Identifier.next()
// 绑定标识符
mutatingSelf.taskIdentifier = issuedIdentifier
// 判断是否显示预加载动画
if base.shouldPreloadAllAnimation() {
options.preloadAllAnimationData = true
}
// 设置进度回调,如果有的话
if let block = progressBlock {
options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
}
// 判断是否设置进度显示的UI
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,
downloadTaskUpdated: {
mutatingSelf.imageTask = $0
},
completionHandler: { result in
CallbackQueue.mainCurrentOrAsync.execute {
// 停止加载动画,如果有的话
maybeIndicator?.stopAnimatingView()
// 判断当前任务id与回调的任务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
// 清除当前任务Id
mutatingSelf.taskIdentifier = nil
switch result {
case .success(let value):
// 是否有设置转场动画, 如果没有直接设置图片
guard self.needsTransition(options: options, cacheType: value.cacheType) else {
mutatingSelf.placeholder = nil
self.base.image = value.image
completionHandler?(result)
return
}
// 开始转场动画,并设置图片
self.makeTransition(image: value.image, transition: options.transition) {
completionHandler?(result)
}
case .failure:
// 加载失败,判断是否有设置失败的图片,如果有设置,则显示失败站位图
if let image = options.onFailureImage {
self.base.image = image
}
completionHandler?(result)
}
}
}
)
// 为当前控件绑定下载任务
mutatingSelf.imageTask = task
return task
}
KingfisherManager.shared.retrieveImage主要流程
- 根据options, source生成context上下文,保存了下载条件的相关资源
- 判断是否需要强制刷新缓存
- 需要: 加载并缓存图片
- 不需要:从缓存中获取图片, 在缓存中获取到了数据,直接返回, 缓存中没有数据,则开始加载数据并缓存
private func retrieveImage(
with source: Source,
context: RetrievingContext,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask? {
let options = context.options
/// 判断是否需要强制刷新
if options.forceRefresh {
/// 加载并缓存图片
return loadAndCacheImage(
source: source,
context: context,
completionHandler: completionHandler)?.value
} else {
/// 从缓存中获取图片
let loadedFromCache = retrieveImageFromCache(
source: source,
context: context,
completionHandler: completionHandler)
/// 在缓存中获取到了数据,直接返回
if loadedFromCache {
return nil
}
if options.onlyFromCache {
let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
completionHandler?(.failure(error))
return nil
}
/// 缓存中没有数据,则开始加载数据并缓存
return loadAndCacheImage(
source: source,
context: context,
completionHandler: completionHandler)?.value
}
}
loadAndCacheImage的流程
- 根据source类型去获取图片
- 从网络上下载,并缓存(
downloader.downloadImage
)
- 获取下载器(
downloader
)
- 下载器开始下载图片, 并缓存(
downloader.downloadImage
)
- 从
provider
中获取,并缓存(由Local Data转换为Image)provideImage
startDownloadTask流程
- 添加下载任务
- 如果任务已经开始,则返回
- 通知外部即将下载图片
- 开始sessionTask, 监听回调
- 任务回调
- 通知外部已经下载了图片数据
- 下载完成,开始将二进制数据转换为image
- 生成图片数据处理器
- 开始将二进制数据转换为image
- 监听处理器处理结果回调,并将处理结果回调给外部
/// 开始下载任务
private func startDownloadTask(
context: DownloadingContext,
callback: SessionDataTask.TaskCallback
) -> DownloadTask
{
/// 添加下载任务
let downloadTask = addDownloadTask(context: context, callback: callback)
let sessionTask = downloadTask.sessionTask
// 如果任务已经开始,则返回
guard !sessionTask.started else {
return downloadTask
}
sessionTask.onTaskDone.delegate(on: self) { (self, done) in
let (result, callbacks) = done
// 通知外部已经下载了图片数据
self.reportDidDownloadImageData(result: result, url: context.url)
switch result {
// 下载完成,开始将二进制数据转换为image
case .success(let (data, response)):
// 生成图片数据处理器
let processor = ImageDataProcessor(
data: data, callbacks: callbacks, processingQueue: context.options.processingQueue
)
// 监听处理器处理结果回调
processor.onImageProcessed.delegate(on: self) { (self, done) in
let (result, callback) = done
self.reportDidProcessImage(result: result, url: context.url, response: response)
let imageResult = result.map { ImageLoadingResult(image: $0, url: context.url, originalData: data) }
let queue = callback.options.callbackQueue
queue.execute { callback.onCompleted?.call(imageResult) }
}
// 开始将二进制数据转换为image
processor.process()
case .failure(let error):
callbacks.forEach { callback in
let queue = callback.options.callbackQueue
queue.execute { callback.onCompleted?.call(.failure(error)) }
}
}
}
/// 通知外部即将下载图片
reportWillDownloadImage(url: context.url, request: context.request)
// 开始sessionTask
sessionTask.resume()
return downloadTask
}
kf前缀(为什么是通过imageView.kf.setImage的方式调用)
- 使用了swift面向协议的方式,为Kingfisher添加了一个类前缀,这样做的目的是为了,防止方法重名冲突,kf相当一个命名空间
/// 定一个Wrapper结构体
public struct KingfisherWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
/// 定义一个协议,什么都不写
public protocol KingfisherCompatible: AnyObject { }
public protocol KingfisherCompatibleValue {}
/// 为协议扩展一个计算属性(kf的类型为KingfisherWrapper,同时指定KingfisherWrapper的泛型为当前调用者Self)
extension KingfisherCompatible {
public var kf: KingfisherWrapper<Self> {
get { return KingfisherWrapper(self) }
set { }
}
}
/// 对应的类型(UIImageView):遵守协议
extension UIImageView: KingfisherCompatible {}
/// 扩展定义的结构,指定Base为UIImageView。 这样就相当于UIImageView中有一个计算属性kf,kf可以调用结构体Wrapper里面的方法与属性
extension KingfisherWrapper where Base == UIImageView {
}
网友评论