iOS-Kingfisher个人浅析(基本结构和下载逻辑篇)

一句最简单的调用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_setobjc_getKingfisherWrapper对象中进行取值和赋值。
而我们知道,Void对象指针作为唯一标识符的Key将相应的数据进行存储或者取出说明了objc_setobjc_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

生成一个网络请求,downloadTimeoutrequestsUsePipelining是下载器内部属性,想要修改就只能在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协议的对象则调用协议函数修改网络请求,进行下一步,optionscallbackQueue默认实现在主线程进行安全的异步回调,感兴趣的可以看一下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, Void> in
            let delegate =  Delegate, 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对象。

整个网络请求前的最后一个环节便完成了,SessionDelegateSessionDataTask内部有相当庞大的代码,难以在此一一描述,只能暂时描述其功能表现为“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, 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函数,其内部调用了URLSessionDataTaskresume函数,由此进行网络请求,而当网络请求完成时调用sessionTask.onTaskDone.delegate(on: self)的闭包实现。
通过分析SessionDelegate关于遵循URLSessionDataDelegate协议的实现协议回调我们可以知道,didReceive data协议函数中负责向SessionDataTask实例对象中不断输写图像数据,而didCompleteWithError协议函数则是当数据全部传输完毕时将完整的图像数据交给内部的onCompleted函数,而onCompleted函数内调用SessionDataTask实例对象的onTaskDone的闭包实现,由此,将图像数据传递给位于Imagedownloader中的SessionDataTaskonTaskDone的闭包实现中。

onTaskDone的闭包实现中我们可以先着眼于这行代码:

let (result, callbacks) = done

代码很简单,就是对元组进行分割,我们需要着眼的是,这个callbacks从何而来。
SessionDelegateonCompleted函数我们可以知道,这个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函数时调用了SessionDataTaskaddCallback函数,传递了SessionDataTask.TaskCallback实例对象,而这个实例对象在初始化时容纳了容纳completionHandleronCompleted对象,而我们知道,completionHandler被调用时,我们位于KingfisherManager中的cacheImage函数就会被调用,在cacheImage中完成缓存图片和继续向外传递图片数据的工作。
所以我们就知道了,当位于ImageDownloader中的sessionTask.onTaskDone的闭包实现中的callbacks数组中的callback对象调用onCompleted时,位于KingfisherManagercacheImage就会被调用。

回到onTaskDone的闭包实现,闭包中当进入下载成功的枚举时,初始化了一个ImageDataProcessor对象,容纳我们上面提到的callbacks。这个ImageDataProcessor对象的功能是负责将Data形式的图像数据转化为Image对象,通过实现onImageProcessed闭包,当ImageDataProcessor调用process函数,运用异步子线程调用doProcess函数内部处理图片完成后获得回调,而onImageProcessed回调时将Image数据插入到ImageLoadingResult中,回到主线程,进行最终的onCompleted回调,调用cacheImage函数,进行相应的工作。
由此,整个网络请求下载图片的逻辑就结束了,接下来是ImageCache相关的内存缓存和磁盘缓存的业务逻辑。

你可能感兴趣的:(iOS-Kingfisher个人浅析(基本结构和下载逻辑篇))