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
,NSImage
和UIButton
的擴(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)
resource
為nil
時(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)cacheKey
為nil
時(shí),取downloadURL.absoluteString
(有興趣的可以去了解一下absoluteString
和path
的區(qū)別)杨蛋。downloadURL
不言而喻兜材,就是圖片的URL
。
Placeholder
public protocol Placeholder {
func add(to imageView: ImageView)
func remove(from imageView: ImageView)
}
Placeholder
是一個(gè)協(xié)議逞力,作者為它定義了add
和remove
方法曙寡,任何。默認(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
里面有receivedSize
和totalSize
,可以根據(jù)這兩個(gè)參數(shù)得知圖片下載了多少和圖片多大,也可以計(jì)算圖片的下載進(jìn)度串稀。
CompletionHandler
public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> ())
CompletionHandler
的回調(diào)里會(huì)有image
除抛、error
、cacheType
母截、imageURL
四個(gè)參數(shù)到忽。image
、error
清寇、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è)
DispatchQueue
:barrierQueue
纺弊、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
磕仅、typealias
、if case let
簸呈、善于利用guard
榕订、擴(kuò)展協(xié)議等等。