源碼閱讀 Kingfisher

Kingfisher是一個(gè)用于圖片下載和緩存的輕量級(jí)、純swift庫。通過喵神的介紹暇矫,可以得知Kingfisher有以下特點(diǎn):

  • 實(shí)現(xiàn)了圖片的異步下載和緩存
  • 基于URLSession的網(wǎng)絡(luò)蔗崎,提供基本圖像處理器和過濾器。
  • 內(nèi)存和磁盤的多層緩存。
  • 可取消下載和處理任務(wù)以提高性能缓艳。
  • 獨(dú)立的組件校摩,根據(jù)需要單獨(dú)使用下載器或緩存系統(tǒng)藕帜。
  • 預(yù)覽圖像并在以后需要時(shí)從緩存中顯示它們丸边。
  • 對(duì)UIImageView,NSImageUIButton的擴(kuò)展题篷,可以直接從URL設(shè)置圖像谎脯。
  • 設(shè)置圖像時(shí)內(nèi)置過渡動(dòng)畫梨熙。
  • 加載圖像時(shí)可自定義占位符隘梨。
  • 可擴(kuò)展的圖像處理和圖像格式支持焕济。

目錄結(jié)構(gòu)

在項(xiàng)目中萌壳,我們使用CocoaPods下載安裝Kingfisher澈蚌。
我們查看Kingfisher的目錄結(jié)構(gòu)摹芙,如下

Kingfisher
  AnimatedImageView.swift    //動(dòng)畫控件   
  Box.swift    //工具類
  CacheSerializer.swift    //序列化類,讀寫文件時(shí)Data和Image互轉(zhuǎn)
  Filter.swift    //僅對(duì)CIImage有效
  FormatIndicatedCacheSerializer.swift    //PNG/JPEG/GIF和Data互轉(zhuǎn)
  Image.swift    //圖片格式轉(zhuǎn)換
  ImageCache.swift    //圖片緩存
  ImageDownloader.swift    //圖片下載
  ImageModifier.swift    //圖片修改
  ImagePrefetcher.swift    //圖片下載管理類宛瞄,對(duì)并發(fā)多個(gè)下載任務(wù)的處理
  ImageProcessor.swift    //數(shù)據(jù)處理類浮禾,將Data轉(zhuǎn)為Image
  ImageTransition.swift    //動(dòng)畫效果
  ImageView+Kingfisher.swift    //擴(kuò)展ImageView添加下載圖片的方法
  Indicator.swift    //動(dòng)畫相關(guān)
  Kingfisher.h    //版本號(hào)
  Kingfisher.swift    //類,擴(kuò)展ImageView添加屬性kf
  KingfisherManager.swift    //管理類坛悉,封裝圖片下載和緩存的邏輯
  KingfisherOptionsInfo.swift    //枚舉類
  Placeholder.swift    //默認(rèn)圖片管理類
  RequestModifier.swift    //協(xié)議伐厌,修改原始URLRequest參數(shù)
  Resource.swift    //協(xié)議,聲明下載鏈接和緩存key
  String+MD5.swift    //MD5加密
  ThreadHelper.swift    //工具類
  UIButton+Kingfisher.swift    //擴(kuò)展UIButton添加下載圖片的方法

調(diào)用方法

很簡(jiǎn)單的一句話:

self.imageV.kf.setImage(with: imgUrl)

如果想在圖片加載的過程中添加默認(rèn)圖片裸影,可以添加placeholder方法挣轨,監(jiān)聽加載的過程progressBlock,圖片加載完成后的回調(diào)completionHandler轩猩。

查看方法

查看kf.setImage方法卷扮,我們會(huì)跳到ImageView+Kingfisher.swift文件里。這里我們對(duì)方法做一個(gè)簡(jiǎn)單的介紹

@discardableResult
public func setImage(with resource: Resource?,
                     placeholder: Placeholder? = nil,
                     options: KingfisherOptionsInfo? = nil,
                     progressBlock: DownloadProgressBlock? = nil,
                     completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
{

}

我們可以看到均践,在這個(gè)方法里面晤锹,包括了ImageView的資源、默認(rèn)圖片彤委、作者封裝的枚舉類鞭铆、加載進(jìn)度的回調(diào)以及完成結(jié)果。
@discardableResult方法是為了取消不使用返回值的警告焦影。
在這個(gè)方法里面(方法太長(zhǎng)就不列舉出來车遂,撿主要的說),

  • 首先判斷參數(shù)合法性斯辰,當(dāng)resourcenil時(shí)舶担,展示默認(rèn)圖片。
  • 設(shè)置讀取策略和啟動(dòng)動(dòng)畫彬呻。比如常見的一個(gè)問題:當(dāng)用戶頭像改變但圖片URL沒有改變時(shí)衣陶,怎么去處理用戶頭像柄瑰。一般有兩種方法,一種是在緩存用戶頭像時(shí)保存當(dāng)前時(shí)間剪况。另一種就是設(shè)置讀取策略教沾,KingfisherOptionsInfo是一個(gè)枚舉,設(shè)置它為forceRefresh時(shí)拯欧,可以強(qiáng)制刷新详囤。
  • 從內(nèi)存、文件或網(wǎng)絡(luò)URL獲取對(duì)應(yīng)圖片數(shù)據(jù)镐作。
  • 獲取圖片完成后藏姐,在主線程刷新界面。

我們查看一下這里面的參數(shù):

Resource

Resource是一個(gè)協(xié)議该贾,我們查看源碼可以看到:

public protocol Resource {
    var cacheKey: String { get }
    var downloadURL: URL { get }
}

cacheKey是圖片保存的key值羔杨,當(dāng)cacheKeynil時(shí),取downloadURL.absoluteString(有興趣的可以去了解一下absoluteStringpath的區(qū)別)杨蛋。downloadURL不言而喻兜材,就是圖片的URL

Placeholder

public protocol Placeholder {
    func add(to imageView: ImageView)
    func remove(from imageView: ImageView)
}

Placeholder是一個(gè)協(xié)議逞力,作者為它定義了addremove方法曙寡,任何。默認(rèn)實(shí)現(xiàn)了Image寇荧,如果想用View充當(dāng)Placeholder举庶,只要讓view遵守協(xié)議即可

extension Placeholder where Self: View{}

KingfisherOptionsInfo

KingfisherOptionsInfo是一個(gè)類型別名,點(diǎn)擊查看

public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]

所以我們關(guān)注的應(yīng)該是KingfisherOptionsInfoItem是什么東西揩抡?那么它是什么呢户侥?

public enum KingfisherOptionsInfoItem {
    case targetCache(ImageCache)    //系統(tǒng)緩存位置÷袜停可以設(shè)置的屬性
    case originalCache(ImageCache)    //系統(tǒng)緩存原始圖像位置(只用于自己設(shè)置placeholder) 
    case downloader(ImageDownloader)    //獲取更改session屬性蕊唐,設(shè)置請(qǐng)求
    case transition(ImageTransition)    //自定義動(dòng)畫
    case downloadPriority(Float)    //下載優(yōu)先級(jí)(0-1)
    case forceRefresh    //每次請(qǐng)求忽略緩存,直接下載
    case fromMemoryCacheOrRefresh    //先取緩存再去文件烁设,再去下載
    case forceTransition    //強(qiáng)制移動(dòng)
    case cacheMemoryOnly     //只從緩存讀取替梨,不讀取本機(jī)沙盒圖片
    case onlyFromCache    //從緩存、沙盒讀取装黑,沒有也不下載網(wǎng)絡(luò)副瀑,顯示placeholder
    case backgroundDecode    //設(shè)置后,顯示前在后臺(tái)線程解碼
    case callbackDispatchQueue(DispatchQueue?)    //自定義回調(diào)隊(duì)列曹体,默認(rèn)主線程
    case scaleFactor(CGFloat)    //自定義圖片data -> Image縮放比例,不指定按屏幕2x\3x縮放
    case preloadAllAnimationData    //預(yù)先加載data成圖片緩存
    case requestModifier(ImageDownloadRequestModifier)    //改變請(qǐng)求
    case processor(ImageProcessor)    //自定義Data轉(zhuǎn)圖片樣式
    case cacheSerializer(CacheSerializer)    //自定義緩存Data 轉(zhuǎn)圖像樣式
    case imageModifier(ImageModifier)    //修改圖像
    case keepCurrentImageWhileLoading     //包含這個(gè)意味著placeHolder設(shè)置無效硝烂,沒有直接用默認(rèn)
    case onlyLoadFirstFrame    //如果返回結(jié)果是.gif圖箕别,只取第一幀顯示
    case cacheOriginalImage    //同時(shí)緩存原始圖片和下載后的圖片
}

DownloadProgressBlock

public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> ())

DownloadProgressBlock里面有receivedSizetotalSize,可以根據(jù)這兩個(gè)參數(shù)得知圖片下載了多少和圖片多大,也可以計(jì)算圖片的下載進(jìn)度串稀。

CompletionHandler

public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> ())

CompletionHandler的回調(diào)里會(huì)有image除抛、errorcacheType母截、imageURL四個(gè)參數(shù)到忽。imageerror清寇、imageURL不做介紹喘漏,直接就能看出什么內(nèi)容。
主要看一下cacheType:

public enum CacheType {
    case none, memory, disk
    public var cached: Bool {
        switch self {
        case .memory, .disk: return true
        case .none: return false
        }
    }
}
  • none檢索圖片時(shí)华烟,圖片尚未緩存
  • memory圖片緩存在內(nèi)存中
  • disk圖片緩存在磁盤中

檢索圖片

OK翩迈,讓我們繼續(xù)看這些代碼。在setImage方法中盔夜,從內(nèi)存负饲、文件或網(wǎng)絡(luò)URL獲取對(duì)應(yīng)圖片數(shù)據(jù)是怎么實(shí)現(xiàn)的呢?這里喂链,我們可以查看KingfisherManager.shared.retrieveImage方法返十。

@discardableResult
public func retrieveImage(with resource: Resource,
    options: KingfisherOptionsInfo?,
    progressBlock: DownloadProgressBlock?,
    completionHandler: CompletionHandler?) -> RetrieveImageTask
{
    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
}

可以看到,代碼會(huì)通過KingfisherOptionsInfo進(jìn)行判斷是強(qiáng)制刷新椭微,網(wǎng)絡(luò)下載并執(zhí)行緩存策略還是從內(nèi)存或文件中獲取對(duì)應(yīng)的Image洞坑。

圖片下載

@discardableResult
    func downloadAndCacheImage(with url: URL,
                             forKey key: String,
                      retrieveImageTask: RetrieveImageTask,
                          progressBlock: DownloadProgressBlock?,
                      completionHandler: CompletionHandler?,
                                options: KingfisherOptionsInfo) -> RetrieveImageDownloadTask?
    {...}

downloadAndCacheImage方法中,會(huì)return downloader.downloadImage方法赏表,下載的主要邏輯在這里實(shí)現(xiàn)检诗,對(duì)應(yīng)的文件是ImageDownloader.swift

@discardableResult
    open func downloadImage(with url: URL,
                       retrieveImageTask: RetrieveImageTask? = nil,
                       options: KingfisherOptionsInfo? = nil,
                       progressBlock: ImageDownloaderProgressBlock? = nil,
                       completionHandler: ImageDownloaderCompletionHandler? = nil) -> RetrieveImageDownloadTask?
    {...}

ImageDownloader.swift文件中瓢剿,主要參數(shù):

  • downloadTimeout 超時(shí)時(shí)間逢慌,默認(rèn)15秒
  • trustedHosts 信任的請(qǐng)求地址,和自己實(shí)現(xiàn)請(qǐng)求代理設(shè)置沖突间狂,二選一
  • sessionConfiguration session配置設(shè)置
  • requestsUsePipelining 請(qǐng)求是否管道類型攻泼,是否按順序下載,默認(rèn)false
  • sessionHandler單獨(dú)設(shè)計(jì)出的一個(gè)ImageDownloaderSessionHandler鉴象,是為了解決之前出現(xiàn)的內(nèi)存泄漏
  • delegate 下載代理
  • authenticationChallengeResponder 信任請(qǐng)求代理忙菠,和trustedHosts沖突二選一
  • fetchLoads 下載完成每個(gè)URL可能有多個(gè)處理方式,優(yōu)先取這里的
  • 此外還有三個(gè)DispatchQueuebarrierQueue纺弊、processQueue牛欢、cancelQueue

下載完成后,在completionHandler回調(diào)中處理圖片淆游,如果下載失敯谩:

if let error = error, error.code == KingfisherError.notModified.rawValue {
    //從緩存中讀取隔盛,不需保存,直接返回
    targetCache.retrieveImage(forKey: key, options: options, completionHandler: { (cacheImage, cacheType) -> () in
        completionHandler?(cacheImage, nil, cacheType, url)
    })
    return
}

下載成功:

if let image = image, let originalData = originalData {
    //存儲(chǔ)圖片
    targetCache.store(image,
                      original: originalData,
                      forKey: key,
                      processorIdentifier:options.processor.identifier,
                      cacheSerializer: options.cacheSerializer,
                      toDisk: !options.cacheMemoryOnly,
                      completionHandler: nil)
    if options.cacheOriginalImage && options.processor != DefaultImageProcessor.default {
        let originalCache = options.originalCache
        let defaultProcessor = DefaultImageProcessor.default
        if let originalImage = defaultProcessor.process(item: .data(originalData), options: options) {
            originalCache.store(originalImage,
                              original: originalData,
                              forKey: key,
                              processorIdentifier: defaultProcessor.identifier,
                              cacheSerializer: options.cacheSerializer,
                              toDisk: !options.cacheMemoryOnly,
                              completionHandler: nil)
        }
    }
}
存儲(chǔ)圖片

我們先來看一下實(shí)現(xiàn)的代碼:

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)
{...}

代碼中memoryCache是:

fileprivate let memoryCache = NSCache<NSString, AnyObject>()

可見圖片存儲(chǔ)首先是緩存在NSCache中拾稳,如果想存儲(chǔ)在磁盤中(if toDisk)吮炕,利用串行隊(duì)列異步的進(jìn)行存儲(chǔ)原圖。

獲取圖片
@discardableResult
open func retrieveImage(forKey key: String,
                           options: KingfisherOptionsInfo?,
                 completionHandler: ((Image?, CacheType) -> ())?) -> RetrieveImageDiskTask?
{...}
  • 首先從內(nèi)存中獲取圖片if let image = self.retrieveImageInMemoryCache(forKey: key, options: options)
  • 如果沒有访得,在根據(jù)條件判斷是否從磁盤上獲取if let image = sSelf.retrieveImageInDiskCache(forKey: key, options: options)
刪除圖片
open func removeImage(forKey key: String,
                      processorIdentifier identifier: String = "",
                      fromDisk: Bool = true,
                      completionHandler: (() -> Void)? = nil)
{...}
@objc public func clearMemoryCache() {...}
open func clearDiskCache(completion handler: (()->())? = nil) {...}
open func cleanExpiredDiskCache(completion handler: (()->())? = nil) {...}
@objc public func backgroundCleanExpiredDiskCache() {...}

其中龙亲,一些方法是通過通知的方法來實(shí)現(xiàn):

// 系統(tǒng)內(nèi)存警告
NotificationCenter.default.addObserver(
    self, selector: #selector(clearMemoryCache), name: .UIApplicationDidReceiveMemoryWarning, object: nil)    
// 程序終止
NotificationCenter.default.addObserver(
    self, selector: #selector(cleanExpiredDiskCache), name: .UIApplicationWillTerminate, object: nil)
// 程序進(jìn)入后臺(tái)
NotificationCenter.default.addObserver(
    self, selector: #selector(backgroundCleanExpiredDiskCache), name: .UIApplicationDidEnterBackground, object: nil)

此外,還有一些屬性要注意:

  • maxMemoryCost最大緩存量悍抑,在收到內(nèi)存警告時(shí)會(huì)被清空鳄炉。
  • pathExtension沙盒后續(xù)拼接文件夾名稱
  • maxCachePeriodInSecond默認(rèn)清除一周前的圖片
  • maxDiskCacheSize沙盒最大存儲(chǔ)量,為0传趾,默認(rèn)無限制

以上就是對(duì)Kingfisher的簡(jiǎn)單描述迎膜,它有很多方法值得我們?nèi)ソ梃b,比如@discardableResult浆兰、where磕仅、typealiasif case let簸呈、善于利用guard榕订、擴(kuò)展協(xié)議等等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜕便,一起剝皮案震驚了整個(gè)濱河市劫恒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轿腺,老刑警劉巖两嘴,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異族壳,居然都是意外死亡憔辫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門仿荆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贰您,“玉大人,你說我怎么就攤上這事拢操〗跻啵” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵令境,是天一觀的道長(zhǎng)杠园。 經(jīng)常有香客問我,道長(zhǎng)舔庶,這世上最難降的妖魔是什么抛蚁? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任玲昧,我火速辦了婚禮,結(jié)果婚禮上篮绿,老公的妹妹穿的比我還像新娘。我一直安慰自己吕漂,他們只是感情好亲配,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惶凝,像睡著了一般吼虎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苍鲜,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天思灰,我揣著相機(jī)與錄音,去河邊找鬼混滔。 笑死洒疚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坯屿。 我是一名探鬼主播油湖,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼领跛!你這毒婦竟也來了乏德?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤吠昭,失蹤者是張志新(化名)和其女友劉穎喊括,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矢棚,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郑什,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幻妓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹦误。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肉津,靈堂內(nèi)的尸體忽然破棺而出强胰,到底是詐尸還是另有隱情,我是刑警寧澤妹沙,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布偶洋,位于F島的核電站,受9級(jí)特大地震影響距糖,放射性物質(zhì)發(fā)生泄漏玄窝。R本人自食惡果不足惜牵寺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恩脂。 院中可真熱鬧帽氓,春花似錦、人聲如沸俩块。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玉凯。三九已至势腮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漫仆,已是汗流浹背捎拯。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盲厌,地道東北人署照。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吗浩,于是被迫代替她去往敵國和親藤树。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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