前言
iOS 开发中,图片网络加载功用几乎是每个开发者都会碰到的需求。因为在 iOS 里面,做这种耗时操作的时分是不建议堵塞主线程的。所以,咱们需求异步下载,下载完成后再回到主线程更新 UI。一般情况下,咱们还需求缓存下载的图片来提高性能节约损耗。
假如这个功用咱们自己完成的话仍是比较费事的。幸运的是,咱们能够运用 Kingfisher 来经过一句代码完成图片异步加载的功用。
在阅览源码之前,希望读者能娴熟的运用该结构,这样才能愈加高效的了解代码。接下来,我将根据该结构 READEME 描绘中的 Features 一项的顺序去整理代码。让咱们开端吧!
Asynchronous image downloading and caching
异步加载图片并缓存,这是该结构的首要功用,也是它存在的意义。首要的运用方法如下:
import Kingfisher
let url = URL(string: "")
imageView.kf.setImage(with: url)
首先,咱们能够看到对 UIImageView
的 kf
特点调用 setImage
函数,传入相应的 URL 就能够加载图片资源了。
为什么这里需求设计一个 kf
扩展特点来去调用 setImage
函数,而不是直接为 UIImageView
供给一个扩展函数去调用呢?这样做的好处便是防止函数重名。比方你自己也想为 UIImageView
供给一个姓名为 setImage
函数去完成自己的逻辑,用扩展特点这种方法就不会影响。该做法类似 Objc 的 XXX 前缀的效果。
kf
完成首要代码:
public struct KingfisherWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
public protocol KingfisherCompatibleValue {}
extension KingfisherCompatible {
/// Gets a namespace holder for Kingfisher compatible types.
public var kf: KingfisherWrapper<Self> {
get { return KingfisherWrapper(self) }
set { }
}
}
extension KFCrossPlatformImage: KingfisherCompatible { }
- KingfisherWrapper 用来包装一下 UIImageView 示例目标,base 存放的便是咱们需求加载图片的 imageView 实例目标。
- 声明 KingfisherCompatibleValue 协议,并为协议扩展一个可读不可写的 kf 特点。
- KFCrossPlatformImage(对应 iOS 即 UIImageView) 恪守该协议。
setImage
完成的首要代码:
public func setImage(
with provider: ImageDataProvider?,
placeholder: Placeholder? = nil,
options: KingfisherOptionsInfo? = nil,
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
{
return setImage(
with: provider,
placeholder: placeholder,
options: options,
progressBlock: nil,
completionHandler: completionHandler
)
}
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
}
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)]
}
let task = KingfisherManager.shared.retrieveImage(
with: source,
options: options,
downloadTaskUpdated: { mutatingSelf.imageTask = $0 },
progressiveImageSetter: { self.base.image = $0 },
referenceTaskIdentifierChecker: { issuedIdentifier == self.taskIdentifier },
completionHandler: { result in
CallbackQueue.mainCurrentOrAsync.execute {
maybeIndicator?.stopAnimatingView()
guard issuedIdentifier == self.taskIdentifier else { ... }
mutatingSelf.imageTask = nil
mutatingSelf.taskIdentifier = nil
switch result {
case .success(let value):
guard self.needsTransition(options: options, cacheType: value.cacheType) else {
mutatingSelf.placeholder = nil
// 为目标 imageView 设置 image
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 {
mutatingSelf.placeholder = nil
self.base.image = image
}
completionHandler?(result)
}
}
}
)
mutatingSelf.imageTask = task
return task
}
- 首先判断了一下 source 是否为空,为空直接回来过错。接着又设置了
placeholder
/taskIdentifier
/preloadAllAnimationData
等特点。 - 重点来了:调用
retrieveImage
函数来异步获取图片,然后在回调中的success
case 中,经过self.base.image = value.image
为 imageView 设置 image。
retrieveImage
完成的首要代码:
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
}
}
- 假如
forceRefresh
为真,则直接下载图片并缓存。 - 假如
loadedFromCache
为真,则代表该图片资源已缓存,直接运用缓存的图片。
主题流程图如下:
Loading image from eitherURLSession
-based networking or local provided data
Kingfisher 不仅能够加载网络图片,它也支持从本地文件 URL 中获取图片。
详细完成:
extension Resource {
public func convertToSource(overrideCacheKey: String? = nil) -> Source {
let key = overrideCacheKey ?? cacheKey
return downloadURL.isFileURL ?
.provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: key)) :
.network(KF.ImageResource(downloadURL: downloadURL, cacheKey: key))
}
}
kf 接受的图片来历参数有必要是一个恪守了 Resource 的协议,该协议的扩展函数里对 URL 类型进行了判断:
- 假如是本地文件,则调用 provider 去加载。
- 假如是网络文件,则调用 network 去加载。