一句最简单的调用Kingfisher的代码:
imgView.kf.setImage(with: URL.init(string: ""))
对象通过调用kf返回一个经过KingfisherWrapper包装的自己,而进入kf相关定义我们可以很明确地了解到kf是一个计算性属性,唯一的作用就是返回一个经过KingfisherWrapper包装的调用者,这个属性被声明并实现于KingfisherCompatible协议的扩展,我们知道,Swift支持在协议的扩展中声明并默认实现属性、初始化函数以及函数等等,而在KingfisherCompatible提供了kf属性的默认实现后,通过:
extension Image: KingfisherCompatible { }
#if !os(watchOS)
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
让我们所知的三类对象遵循该协议,它们也就默认实现了kf属性。
而通过观察KingfisherWrapper的主代码我们也可以知道,KingfisherWrapper主代码仅仅是将调用者保存在自身中,关于Image、UIButton、UIImageView的实现都被分别写在相应的扩展中。
而当我们进入setImage函数的源码可知,该函数位于KingfisherWrapper的扩展,并且是实现在当KingfisherWrapper持有的Base属于UIImageView类型的扩展中。
setImage函数接收一个遵循Resource协议的形参,而Kingfisher在源码中实现了URL的扩展遵循Resource协议
extension URL: Resource {
public var cacheKey: String { return absoluteString }
public var downloadURL: URL { return self }
}
其作用在于使用计算性属性返回String类型的URL地址作为缓存标识符。
而在setImage函数内部,遵循Resource协议的URL地址会通过map高阶函数转化为Source枚举对象,这个枚举对象的作用是划分对象是网络图片亦或是本地图片,同时,实现一些附属属性以及计算性属性。而我们的URL地址在此被包裹为Source.network类型的枚举对象进入下一阶段。
guard let source = source else {
mutatingSelf.placeholder = placeholder
mutatingSelf.taskIdentifier = nil
completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource)))
return nil
}
大部分的网络请求数据以外的业务逻辑都暂时先跳过,但这里的代码的表现形式的内部源码与Kingfisher的一些比较重要的业务逻辑有比较深的关系,因此提前写出。
我们知道,这里的调用者mutatingSelf并不是我们外部的调用者,而是包裹了我们的调用者的KingfisherWrapper对象,而代码中的placeholder、taskIdentifier以及这里没有写出来的indicatorType、indicator、imageTask都存在于KingfisherWrapper当包裹对象为ImageView时的扩展中,它们都实现了相同的概念,利用一个Void对象的指针地址作为唯一标识符调用objc_set和objc_get在KingfisherWrapper对象中进行取值和赋值。
而我们知道,Void对象指针作为唯一标识符的Key将相应的数据进行存储或者取出说明了objc_set和objc_get本身能在一个对象的属性列表中插入没有声明过的属性,并对这个属性进行赋值取值。
let task = KingfisherManager.shared.retrieveImage(
with: source,
options: options,
completionHandler: { result in
}
这一段是Kingfisher的核心代码,让我们看看它的内部实现是什么样子的。
因为KingfisherManager承担了整个框架的核心逻辑,并且极大部分的代码都与这个类有关,如果我们直接从KingfisherManager的单例方法初始化函数开始进行解析,只会看到大量分别承担不同功能的对象,并且这些承担不同功能的对象内部还有分门别类的子对象,直到我们看到作为基本单元的最底层的子对象为止,这样就相当于盲人摸象,管中窥豹难见一斑,所以,先从retrieveImage函数的功能实现来反向推测KingfisherManager实现了什么样的对象来帮助自己实现什么样的功能。
//options: KingfisherParsedOptionsInfo
if options.forceRefresh {
return loadAndCacheImage(
source: source,
options: options,
completionHandler: completionHandler)?.value
}
我们知道,当我们调用kf.setImage函数时,KingfisherOptionsInfo作为可选形参,其实质是一个KingfisherOptionsInfoItem数组,而在setImage函数内部,我们将KingfisherOptionsInfo转化为KingfisherParsedOptionsInfo对象,其内部通过for循环将KingfisherOptionsInfoItem一个个地配置成相应的KingfisherParsedOptionsInfo成员变量,就如上述代码的forceRefresh布尔值,我们可以通过:
var options = KingfisherOptionsInfo.init()
options.append(KingfisherOptionsInfoItem.forceRefresh)
来添加到options形参中。
我们来到loadAndCacheImage函数的内部实现中,最先看到的是一个巨大的内部实现函数cacheImage,我们暂时先跳过这个函数,进入到下一部分:
switch source {
case .network(let resource):
let downloader = options.downloader ?? self.downloader
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
}
如果我们是网络图片的话就会进入到.network逻辑中,KingfisherManager在单例中调用初始化函数,默认生成了一个名为default的下载器,如果我们没有在KingfisherOptionsInfo中另外提供下载器,那么它就会使用默认下载器执行代码。
当下载机调用downloadImage函数时,我们将cacheImage函数作为形参传入,而下载器内部进行下载工作时无论成功与否,最终都会回调给cacheImage函数,而cacheImage函数正如其名,负责执行成功后的缓存工作以及图像回调还有失败后的失败回调。
缓存的相关逻辑之后再说,先进入下载器downloadImage函数的内部构造(因为函数内部代码比较庞大,因此分段解析):
// Creates default request.
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
request.httpShouldUsePipelining = requestsUsePipelining
生成一个网络请求,downloadTimeout和requestsUsePipelining是下载器内部属性,想要修改就只能在options中初始化一个下载器替代默认下载器重新配置相关属性。
if let requestModifier = options.requestModifier {
// Modifies request before sending.
guard let r = requestModifier.modified(for: request) else {
options.callbackQueue.execute {
completionHandler?(.failure(KingfisherError.requestError(reason: .emptyRequest)))
}
return nil
}
request = r
}
如果options含有遵循ImageDownloadRequestModifier协议的对象则调用协议函数修改网络请求,进行下一步,options的callbackQueue默认实现在主线程进行安全的异步回调,感兴趣的可以看一下CallbackQueue的内部实现。
// There is a possibility that request modifier changed the url to `nil` or empty.
// In this case, throw an error.
guard let url = request.url, !url.absoluteString.isEmpty else {
options.callbackQueue.execute {
completionHandler?(.failure(KingfisherError.requestError(reason: .invalidURL(request: request))))
}
return nil
}
判断URL是否为空的判断,如注释所说,外部形参传入的Source
为空的判断很早就做过了,这里主要是实现ImageDownloadRequestModifier协议函数的对象的相关判断。
// Wraps `completionHandler` to `onCompleted` respectively.
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)
let callback = SessionDataTask.TaskCallback(
onCompleted: onCompleted,
options: options
)
// Ready to start download. Add it to session task manager (`sessionHandler`)
let downloadTask: DownloadTask
if let existingTask = sessionDelegate.task(for: url) {
downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback)
} else {
let sessionDataTask = session.dataTask(with: request)
sessionDataTask.priority = options.downloadPriority
downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback)
}
let sessionTask = downloadTask.sessionTask
这部分代码紧密嵌合在一起,完成网络请求之前的最后一部分工作,我们一点点捋过来。
首先是第一部分,初始化一个onCompleted对象,其实质是将外界传递进来的completionHandler用一个Delegate类进行容纳,这个类的内部实现不多,其主要功能就是容纳相应的两个泛型,内部命名为输入与输出,用一个带闭包的函数将传入的target置为弱引用并按照原本的格式返回两个泛型对象,达到避免循环引用的作用,可以说,这个类是防止循环引用而对闭包的封装。
然后,实现Delegate实例对象的闭包函数,并将delegate实例对象通过map高阶函数返回命名为onCompleted,当onCompleted在其他地方被调用时,这里实现的闭包就会被调用,并将最终的值传递给外界,即传递到cacheImage函数中。
第二部分,传入onComplete对象和配置对象,生成SessionDataTask.TaskCallback结构体的实例对象。
第三部分,判断我们的sessionDelegate实例对象内部维护的以URL作为Key,SessionDataTask作为Value的哈希表中是否已有相应的实例对象,若存在则使用SessionDataTask实例对象生成DownloadTask对象,若不存在,则从URLSession中获取URLSessionTask对象,并将其通过sessionDelegate包装为SessionDataTask对象,实现其相应闭包,插入到哈希表中并生成DownloadTask对象。
整个网络请求前的最后一个环节便完成了,SessionDelegate及SessionDataTask内部有相当庞大的代码,难以在此一一描述,只能暂时描述其功能表现为“SessionDelegate负责实现URLSession的协议方法,负责在各个代理回调中处理相应的业务逻辑,同时暴露监听回调给外部,通常这个实现其监听回调的对象是ImageDownloader”。
// Start the session task if not started yet.
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()
}
首先先看代码的最后数行,分别是通知代理者的协议函数,与SessionDataTask调用resume函数,其内部调用了URLSessionDataTask的resume函数,由此进行网络请求,而当网络请求完成时调用sessionTask.onTaskDone.delegate(on: self)的闭包实现。
通过分析SessionDelegate关于遵循URLSessionDataDelegate协议的实现协议回调我们可以知道,didReceive data协议函数中负责向SessionDataTask实例对象中不断输写图像数据,而didCompleteWithError协议函数则是当数据全部传输完毕时将完整的图像数据交给内部的onCompleted函数,而onCompleted函数内调用SessionDataTask实例对象的onTaskDone的闭包实现,由此,将图像数据传递给位于Imagedownloader中的SessionDataTask的onTaskDone的闭包实现中。
在onTaskDone的闭包实现中我们可以先着眼于这行代码:
let (result, callbacks) = done
代码很简单,就是对元组进行分割,我们需要着眼的是,这个callbacks从何而来。
在SessionDelegate的onCompleted函数我们可以知道,这个callbacks的数据实际上是执行这次请求任务的SessionDataTask的实例对象中的计算性属性callbacks从哈希表成员变量callbacksStore中获取的,那callbacksStore的数据从何而来呢?
观察SessionDataTask的内部代码我们可以发现,唯一向callbacksStore表中插入数据的函数就是addCallback函数,而这个函数就是在我们之前看过的ImageDownloader的初始化DownloadTask的相关代码中。
let downloadTask: DownloadTask
if let existingTask = sessionDelegate.task(for: url) {
downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback)
} else {
let sessionDataTask = session.dataTask(with: request)
sessionDataTask.priority = options.downloadPriority
downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback)
}
sessionDelegate调用append或者add函数时调用了SessionDataTask的addCallback函数,传递了SessionDataTask.TaskCallback实例对象,而这个实例对象在初始化时容纳了容纳completionHandler的onCompleted对象,而我们知道,completionHandler被调用时,我们位于KingfisherManager中的cacheImage函数就会被调用,在cacheImage中完成缓存图片和继续向外传递图片数据的工作。
所以我们就知道了,当位于ImageDownloader中的sessionTask.onTaskDone的闭包实现中的callbacks数组中的callback对象调用onCompleted时,位于KingfisherManager的cacheImage就会被调用。
回到onTaskDone的闭包实现,闭包中当进入下载成功的枚举时,初始化了一个ImageDataProcessor对象,容纳我们上面提到的callbacks。这个ImageDataProcessor对象的功能是负责将Data形式的图像数据转化为Image对象,通过实现onImageProcessed闭包,当ImageDataProcessor调用process函数,运用异步子线程调用doProcess函数内部处理图片完成后获得回调,而onImageProcessed回调时将Image数据插入到ImageLoadingResult中,回到主线程,进行最终的onCompleted回调,调用cacheImage函数,进行相应的工作。
由此,整个网络请求下载图片的逻辑就结束了,接下来是ImageCache相关的内存缓存和磁盘缓存的业务逻辑。