Kingfisher源碼淺析

源碼的閱讀是提升編程技能的一種方法,懷著學(xué)習(xí)及忐忑的心情準(zhǔn)備對(duì)喵神的Kingfisher庫的源碼進(jìn)行下閱讀和理解,提高下自己的整體編程思路以及良好編程習(xí)慣的養(yǎng)成,話不多說,現(xiàn)在開擼。
注:本文基于Kingfisher4.10.0版本

一、框架構(gòu)成

1 kf組成

1.1.1 使用Kingfisher時(shí)候,.kf是使用最普遍的,其實(shí)提供的kf本質(zhì)上市一個(gè)模板結(jié)構(gòu)體KingfisherWrapper
public struct KingfisherWrapper<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}
1.1.2 通過協(xié)議配置到UI和String等上

模板結(jié)構(gòu)體KingfisherWrapper通過協(xié)議 KingfisherCompatible配置到Image,ImageView,Button及 tvOSTVMonogramView上, 協(xié)議 KingfisherCompatibleValue 配置到Data, String, CGSize上。

協(xié)議定義及extension:

/**
 A type that has Kingfisher extensions.
 */
public protocol KingfisherCompatible {
    associatedtype CompatibleType
    var kf: CompatibleType { get }
}

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

extension Image: KingfisherCompatible { }
#if !os(watchOS)
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
#else
extension WKInterfaceImage: KingfisherCompatible { }
#endif

2. kf最普遍的在imageview的使用

1.2.1 在實(shí)際的開發(fā)中,我們使用最多的就是ImageView上的kf,示例代碼如下:
let imageView = UIImageView()
imageView.kf.setImage(with: url)
// 帶背景圖片
let image = UIImage(named: "default_profile_icon")
imageView.kf.setImage(with: url, placeholder: image)
1.2.2 對(duì)應(yīng)的實(shí)現(xiàn)在ImageView+Kingfisher文件中:
extension Kingfisher where Base: ImageView {}
1.2.3 其中最重要的方法就是setImage這個(gè)方法:
@discardableResult
    public func setImage(with resource: Resource?,
                         placeholder: Placeholder? = nil,
                         options: KingfisherOptionsInfo? = nil,
                         progressBlock: DownloadProgressBlock? = nil,
                         completionHandler: CompletionHandler? = nil) -> RetrieveImageTask{}

這個(gè)方法是根據(jù)KingfisherOptionsInfo作為配置,設(shè)置ResourcePlaceholder,通過KingfisherManager.shared.retrieveImage方法下載數(shù)據(jù)的過程。

接下來一個(gè)個(gè)分析下這幾個(gè)參數(shù):

  • Resource:
    Resource是一個(gè)協(xié)議,標(biāo)志著圖片來自網(wǎng)絡(luò),提供cacheKeydownloadURL,對(duì)應(yīng)代碼分析如下:
public protocol Resource {
    // 緩存的key
    var cacheKey: String { get }
    
    // 下載鏈接
    var downloadURL: URL { get }
}

/// ImageResource對(duì)應(yīng)的結(jié)構(gòu)體
public struct ImageResource: Resource {
    
    public let cacheKey: String
    
    public let downloadURL: URL
    
   // Create a resource.
    public init(downloadURL: URL, cacheKey: String? = nil) {
        self.downloadURL = downloadURL
        self.cacheKey = cacheKey ?? downloadURL.absoluteString
    }
}

/// URL實(shí)現(xiàn)該協(xié)議,將absoluteString作為緩存的Key
extension URL: Resource {
    public var cacheKey: String { return absoluteString }
    public var downloadURL: URL { return self }
}
  • Placeholder:
    ``Placeholder也是一個(gè)協(xié)議,提供在ImageView`上添加自身和移除自身的功能,對(duì)應(yīng)代碼分析如下:
public protocol Placeholder {
    
    func add(to imageView: ImageView)
 
    func remove(from imageView: ImageView)
}

其中Image和View都有對(duì)應(yīng)的協(xié)議實(shí)現(xiàn):

extension Placeholder where Self: Image {
    
    /// 設(shè)置imageview的image為自己
    public func add(to imageView: ImageView) { imageView.image = self }
    
    /// 將imageview的image設(shè)置為nil
    public func remove(from imageView: ImageView) { imageView.image = nil }
}
extension Placeholder where Self: View {
    
    /// 將自身即view覆蓋到imageview上
    public func add(to imageView: ImageView) {
        imageView.addSubview(self)

        self.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: imageView, attribute: .centerX, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: imageView, attribute: .centerY, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: imageView, attribute: .height, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: imageView, attribute: .width, multiplier: 1, constant: 0)
            ])
    }

    /// 將自身移除
    public func remove(from imageView: ImageView) {
        self.removeFromSuperview()
    }
}
  • KingfisherOptionsInfo

KingfisherOptionsInfos是配置的數(shù)組,而KingfisherOptionsInfoItem是一個(gè)枚舉,包含各種可以配置的信息,代碼分析如下:

/// 配置的數(shù)組
public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]
let KingfisherEmptyOptionsInfo = [KingfisherOptionsInfoItem]()

/// 枚舉
public enum KingfisherOptionsInfoItem {
    case targetCache(ImageCache) //設(shè)置緩存器,Kingfisher用這個(gè)緩存器來緩存展示的圖片
    case originalCache(ImageCache) //設(shè)置緩存器,Kingfisher用這個(gè)緩存器來緩存下載的原始圖片
    case downloader(ImageDownloader) //設(shè)置下載器,Kingfisher用這個(gè)下載器來下載數(shù)據(jù)
    case transition(ImageTransition) //設(shè)置下載完成之后的動(dòng)畫
    case downloadPriority(Float) //0.0~1.0 設(shè)置下載優(yōu)先級(jí)
    case forceRefresh //忽視緩存
    case fromMemoryCacheOrRefresh //先嘗試從內(nèi)存緩存讀取,如果沒有,則重新下載,不會(huì)讀取磁盤緩存
    case forceTransition //從緩存讀取的圖片也會(huì)進(jìn)行動(dòng)畫處理
    case cacheMemoryOnly //只通過內(nèi)存緩存圖片
    case waitForCache //緩存完成之后才調(diào)用completion block
    case onlyFromCache  //只通過緩存讀取圖片,不會(huì)下載
    case backgroundDecode //使用圖片前線在后臺(tái)線程上解碼
    case callbackDispatchQueue(DispatchQueue?) //設(shè)置回調(diào)在那個(gè)隊(duì)列上
    case scaleFactor(CGFloat) // data轉(zhuǎn)成Image時(shí)的Scale
   .....
}

至于 DownloadProgressBlockCompletionHandler這兩個(gè)下載進(jìn)度閉包和完成的閉包,需要對(duì)KingfisherManager進(jìn)行分析下了。

3. KingfisherManager(下載及緩存)

KingfisherManager為一個(gè)單例類,主要由兩部分組成,ImageDownloader用于管理下載;ImageCache用于管理緩存。

public class KingfisherManager {
    
    public static let shared = KingfisherManager()
    
    // 緩存
    public var cache: ImageCache
    
    /// 下載
    public var downloader: ImageDownloader

    // 便利構(gòu)造
    convenience init() {
        self.init(downloader: .default, cache: .default)
    }
    
    // 初始化
    init(downloader: ImageDownloader, cache: ImageCache) {
        self.downloader = downloader
        self.cache = cache

        let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)"
        processQueue = DispatchQueue(label: processQueueName, attributes: .concurrent)
    }
}
1.3.1 其中主要的方法是retrieveImage,用于生成下載任務(wù),代碼分析如下:
   @discardableResult
    public func retrieveImage(with resource: Resource, //resource看前面的分析
        options: KingfisherOptionsInfo?, //配置信息
        progressBlock: DownloadProgressBlock?,
        completionHandler: CompletionHandler?) -> RetrieveImageTask //返回值為task
    {
        let task = RetrieveImageTask()
        let options = currentDefaultOptions + (options ?? KingfisherEmptyOptionsInfo)
        if options.forceRefresh {
              // 下載及緩存圖片
            _ = downloadAndCacheImage(
                with: resource.downloadURL,
                forKey: resource.cacheKey,
                retrieveImageTask: task,
                progressBlock: progressBlock,
                completionHandler: completionHandler,
                options: options)
        } else {
            // 讀出緩存中的圖片
            tryToRetrieveImageFromCache(
                forKey: resource.cacheKey,
                with: resource.downloadURL,
                retrieveImageTask: task,
                progressBlock: progressBlock,
                completionHandler: completionHandler,
                options: options)
        }
        
        return task
    }
1.3.2 圖片下載ImageDownloader

Source為ImageDataProvider的部分較為簡單,就是調(diào)用ImageDataProvider的data方法獲取Image,所以這里我們只討論Source為Resource,也就是從網(wǎng)絡(luò)上獲取圖片的部分。

下載圖片的代碼如下:

let downloader = options.downloader ?? ImageDownloader.default
guard let task = downloader.downloadImage(
    with: resource.downloadURL,
    options: options,
    completionHandler: cacheImage) else {
  return nil
}
return .download(task)

ImageDownloader是圖片的下載器, 本類中需要注意的成員變量有如下幾個(gè):

網(wǎng)絡(luò)請求的抽象:

private let sessionDelegate: SessionDelegate
private var session: URLSession
open var sessionConfiguration = URLSessionConfiguration.ephemeral {
  didSet {
    session.invalidateAndCancel()
    session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
  }
}

// 自定義證書的驗(yàn)證邏輯
// https://www.cnblogs.com/Code-life/p/7806824.html
open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable?

二、收獲總結(jié)

1. tyoealias運(yùn)用
在編程中要善于使用`typealias`,例如在一個(gè)文件中大量使用同一個(gè)系統(tǒng)類時(shí),就可以使用別名將其替換,這個(gè)后期修改這個(gè)類的話就簡單多了。
2. 靈活使用protocol

在看完源碼后,可以很清楚的看到里面靈活使用了多個(gè)協(xié)議,協(xié)議的使用使得代碼更多解耦和靈活,特別是swift中對(duì)協(xié)議的extension,簡直是協(xié)議的利器。

3. 一些配置信息可使用枚舉的數(shù)組來靈活配置

遇到有多種情況,需要配置時(shí)候,可以使用枚舉來定義不同的類型,通過一個(gè)數(shù)組和靈活配置這些信息。

4. kf 寫法

在SnapKit中,view.snp是通過對(duì)View進(jìn)行擴(kuò)展實(shí)現(xiàn)的
類似snp的寫法:

public var snp: ConstraintViewDSL {
        return ConstraintViewDSL(view: self)
    }

這種寫法來為類添加一個(gè)不存在的屬性

在KingFisher中,是通過泛型與協(xié)議結(jié)合的方式實(shí)現(xiàn)的:

/**
 A type that has Kingfisher extensions.
 */
public protocol KingfisherCompatible {
    associatedtype CompatibleType
    var kf: CompatibleType { get }
}

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

extension ImageView: KingfisherCompatible { }
extension Image: KingfisherCompatible { }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容