Kingfisher原始碼閱讀(一)

swordjoy發表於2019-03-04

一、前言

Kingfisher Github地址

Kingfisherswift語言編寫的一款非常受歡迎的圖片載入庫,功能和OC語言編寫的SDWebImage類似。作者貓神是我初入iOS開發到現在都很崇拜的偶像。


二、Kingfisher的一般使用

imageView.kf.setImage(with: imageURL)
複製程式碼

從上面的使用方法可以看出Kingfisher的使用方法非常簡單,那麼裡面是怎麼實現的呢?


三、 主要流程

1. Kingfisher.swift檔案中

關鍵點: Kingfisher

不知道你是否對上面使用方法中的kf好奇,我記得我第一次使用的時候,還不是這種寫法。下面來揭開它的神祕面紗:

public final class Kingfisher<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

public protocol KingfisherCompatible {
    associatedtype CompatibleType
    var kf: CompatibleType { get }
}

public extension KingfisherCompatible {
    public var kf: Kingfisher<Self> {
        get { return Kingfisher(self) }
    }
}

extension ImageView: KingfisherCompatible { }
複製程式碼

這裡的Image是為了適配多款系統而typealias的一個型別別名,在AppKit中為NSImage,UIKit中為UIImage

1.先定義了一個不可繼承的Kingfisher類,他有一個泛型屬性base。

2.然後定義了一個KingfisherCompatible協議,定義了一個只讀的kf關聯型別屬性。

3.在擴充套件中實現了KingfisherCompatible協議,指定關聯型別為Kingfisher<Self>,這裡的Self理解為協議約束,需要遵守KingfisherCompatible協議的型別,例如這裡的就是Image

4.ImageView遵守KingfisherCompatible協議。

然後就可以使用了,ImageView+Kingfisher.swift中:

extension Kingfisher where Base: ImageView {
    // 省略
}
複製程式碼

這裡看上去是在給Kingfisher新增擴充套件,其實是給ImageView,因為Kingfisher中的base屬性其實就是ImageView的例項物件,我們只需要在新增的方法中用base代替我們直接給UIImageView新增擴充套件中的self就行了。

但是目前這種寫法有一個限制,那就是結構體無法使用,因為Kingfisher<Self>中的Self是不支援結構體的,如果結構體也想要使用這種方法,那只有單獨寫,例如String+MD5.swift檔案中的:

public struct StringProxy {
    fileprivate let base: String
    init(proxy: String) {
        base = proxy
    }
}

extension String: KingfisherCompatible {
    public typealias CompatibleType = StringProxy
    public var kf: CompatibleType {
        return StringProxy(proxy: self)
    }
}
複製程式碼

這裡和上面類似就不贅述了,我們自己的寫的工具庫或者三方也能自定義這樣的寫法來避免衝突.


2. ImageView+Kingfisher.swift中

關鍵點: KingfisherOptionsInfo是一個列舉,用來配置庫中的資訊,例如後臺執行緒解碼圖片,和出現的動畫等等.

這個方法主要配置了一些資訊,例如預設圖片和載入指示器等等.然後就將獲取圖片的任務轉交給了KingfisherManager
獲取圖片成功後,根據配置的顯示動畫,獲取顯示並動畫顯示圖片

公開分類方法

public func setImage(with resource: Resource?,
                     placeholder: Placeholder? = nil,
                     options: KingfisherOptionsInfo? = nil,
                     progressBlock: DownloadProgressBlock? = nil,
                     completionHandler: CompletionHandler? = nil) 
                     -> RetrieveImageTask
{
    //  省略 
    //  -> 3 KingfisherOptionsInfo配置後等,將任務交給KingfisherManager。
    //  根據配置的動畫,獲取顯示並動畫顯示圖片
}
複製程式碼

3.KingfisherManager.swift中

關鍵點: KingfisherManager,用來獲取圖片和下載快取圖片。

獲取圖片:

public func retrieveImage(with resource: Resource,
        options: KingfisherOptionsInfo?,
        progressBlock: DownloadProgressBlock?,
        completionHandler: CompletionHandler?) -> RetrieveImageTask
{
    //  省略
    // -> 4.1 如果強制重新整理,去下載並快取
    // -> 4.2 從快取中獲取
}
複製程式碼

4.1 KingfisherManager.swift中

關鍵點: ImageDownloader,圖片下載器。

關鍵點: ImageCache,圖片快取器。

關鍵點: ImageProcessor,圖片處理器。

將任務直接交給ImageDownloader,下載成功後用ImageCache來快取圖片,如果有對圖片的處理配置,ImageProcessItem還會對圖片進行處理。

@discardableResult
func downloadAndCacheImage(with url: URL,
                             forKey key: String,
                      retrieveImageTask: RetrieveImageTask,
                          progressBlock: DownloadProgressBlock?,
                      completionHandler: CompletionHandler?,
                                options: KingfisherOptionsInfo) 
                                -> RetrieveImageDownloadTask?
{
    // 省略
    // -> 5.1 直接將任務交給了ImageDownloader
}
複製程式碼

4.2 KingfisherManager.swift中

直接將任務交給了ImageCache,嘗試從快取中獲取圖片

  • 按照配置能獲取到圖片,直接返回
  • 沒有配置圖片的處理,並且沒有獲取到圖片,回到4.1重新下載
  • 有配置圖片的處理,嘗試獲取圖片的原圖,如果有,處理並返回,沒有回到4.1重新下載.
func tryToRetrieveImageFromCache(forKey key: String,
                                 with url: URL,
                                 retrieveImageTask: RetrieveImageTask,
                                 progressBlock: DownloadProgressBlock?,
                                 completionHandler: CompletionHandler?,
                                 options: KingfisherOptionsInfo)
{
    // 省略
    // 5.2 -> 直接將任務交給了ImageCache
}
複製程式碼

5.1.1 ImageDownloader.swift中

關鍵點: ImageDownloader,圖片下載器

下載圖片

downloadImage(with url: URL,
                       retrieveImageTask: RetrieveImageTask? = nil,
                       options: KingfisherOptionsInfo? = nil,
                       progressBlock: ImageDownloaderProgressBlock? = nil,
                       completionHandler: ImageDownloaderCompletionHandler? = nil) 
                       -> RetrieveImageDownloadTask?
{
    // 省略
    // -> 完
}
複製程式碼

5.1.2 ImageCache.swift中

快取圖片,分為記憶體快取和磁碟快取

open func store(_ image: Image,
                      original: Data? = nil,
                      forKey key: String,
                      processorIdentifier identifier: String = "",
                      cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default,
                      toDisk: Bool = true,
                      completionHandler: (() -> Void)? = nil)
{
    // 省略 
    // 記憶體快取
    // 磁碟快取
    // -> 完
}
複製程式碼

5.2 ImageCache.swift中

根據配置從記憶體獲取或者從磁碟獲取獲取圖片.

@discardableResult
open func retrieveImage(forKey key: String,
                        options: KingfisherOptionsInfo?,
                        completionHandler: ((Image?, CacheType) -> Void)?) 
                        -> RetrieveImageDiskTask?
{
    // 省略 
    // 根據配置從記憶體獲取或者從磁碟獲取
}
複製程式碼

四、後記

這篇這一篇文章主要分析Kingfisher工作的主要流程,細節待後續文章分享。其中還有很多枝葉操作也是很有意思的。

相關文章