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的部分較為簡(jiǎn)單,就是調(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ò)請(qǐng)求的抽象:

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è)類的話就簡(jiǎn)單多了盔腔。
2. 靈活使用protocol

在看完源碼后杠茬,可以很清楚的看到里面靈活使用了多個(gè)協(xié)議,協(xié)議的使用使得代碼更多解耦和靈活弛随,特別是swift中對(duì)協(xié)議的extension澈蝙,簡(jiǎn)直是協(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末链烈,一起剝皮案震驚了整個(gè)濱河市厉斟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌强衡,老刑警劉巖擦秽,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異漩勤,居然都是意外死亡感挥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門越败,熙熙樓的掌柜王于貴愁眉苦臉地迎上來触幼,“玉大人,你說我怎么就攤上這事究飞≈们” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵亿傅,是天一觀的道長媒峡。 經(jīng)常有香客問我,道長葵擎,這世上最難降的妖魔是什么谅阿? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上奔穿,老公的妹妹穿的比我還像新娘镜沽。我一直安慰自己敏晤,他們只是感情好贱田,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘴脾,像睡著了一般男摧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上译打,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天耗拓,我揣著相機(jī)與錄音,去河邊找鬼奏司。 笑死乔询,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的韵洋。 我是一名探鬼主播竿刁,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼搪缨!你這毒婦竟也來了食拜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤副编,失蹤者是張志新(化名)和其女友劉穎负甸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痹届,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呻待,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了队腐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片带污。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖香到,靈堂內(nèi)的尸體忽然破棺而出鱼冀,到底是詐尸還是另有隱情,我是刑警寧澤悠就,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布千绪,位于F島的核電站,受9級(jí)特大地震影響梗脾,放射性物質(zhì)發(fā)生泄漏荸型。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一炸茧、第九天 我趴在偏房一處隱蔽的房頂上張望瑞妇。 院中可真熱鬧稿静,春花似錦、人聲如沸辕狰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔓倍。三九已至悬钳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間偶翅,已是汗流浹背默勾。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留聚谁,地道東北人母剥。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像形导,于是被迫代替她去往敵國和親环疼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容