前言

iOS 开发中,图片网络加载功用几乎是每个开发者都会碰到的需求。因为在 iOS 里面,做这种耗时操作的时分是不建议堵塞主线程的。所以,咱们需求异步下载,下载完成后再回到主线程更新 UI。一般情况下,咱们还需求缓存下载的图片来提高性能节约损耗。

假如这个功用咱们自己完成的话仍是比较费事的。幸运的是,咱们能够运用 Kingfisher 来经过一句代码完成图片异步加载的功用。

在阅览源码之前,希望读者能娴熟的运用该结构,这样才能愈加高效的了解代码。接下来,我将根据该结构 READEME 描绘中的 Features 一项的顺序去整理代码。让咱们开端吧!

Asynchronous image downloading and caching

异步加载图片并缓存,这是该结构的首要功用,也是它存在的意义。首要的运用方法如下:

import Kingfisher
let url = URL(string: "")
imageView.kf.setImage(with: url)

首先,咱们能够看到对 UIImageViewkf 特点调用 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 为真,则代表该图片资源已缓存,直接运用缓存的图片。

主题流程图如下:

源码阅览系列之图片加载结构:Kingfisher (第一篇)

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 去加载。