序言
Kingfisher
是喵神的一個(gè)異步下載和緩存圖片的Swift庫,類似于OC 的SDWebImage
中文簡介缀旁,github地址 最近才開始學(xué)習(xí)swift, 所以對于之前的swift 版本不是很了解,直接以最新版本來學(xué)習(xí)了 由于個(gè)人水平有限 如有錯(cuò)誤還望包涵
一、Kingfisher的架構(gòu)
閱讀他人優(yōu)秀代碼是一個(gè)提高自身代碼水平很好的方法鲸湃。花了幾天的時(shí)間子寓,看了Kingfisher
的源代碼暗挑,里面包含的很多知識點(diǎn),讓我受益匪淺斜友。3.x版本相比與之前的版本炸裆,一個(gè)重要的改變就是protocol
的靈活運(yùn)用,更加面向協(xié)議編程鲜屏。當(dāng)然還有其他很多知識烹看,比如多線程,枚舉洛史,閉包惯殊,Extension 等等應(yīng)用。 Kingfisher中
有Core
也殖,Extension
土思,Helpers
三個(gè)目錄結(jié)構(gòu) 共20個(gè)文件
Core
image.swift
文件內(nèi)部對 UIImage 以及 NSData 進(jìn)行了拓展, 包含判定圖片類型、圖片解碼以及Gif數(shù)據(jù)處理等操作
Indicator.swift
圖片加載時(shí)loading指示
ImageCache.swift
主要負(fù)責(zé)將加載過的圖片緩存至本地忆嗜。
ImageDownloader.swift
負(fù)責(zé)下載網(wǎng)絡(luò)圖片己儒。
ImagePrefetcher.swift
可用于提前指定一些圖片下載
ImageProcessor.swift
可用于將下載的數(shù)據(jù)合成圖片對象
CacheSerializer.swift
可用于圖像對象序列化成圖像數(shù)據(jù)存儲到磁盤緩存和從磁盤緩存將圖片數(shù)據(jù)反序列化成圖像對象。
RequestModifier.swift
下載圖像請求修改器捆毫。
ImageTransition.swift
過渡動畫效果 使用UIViewAnimationOptions
動畫效果
KingfisherManager.swift
Kingfisher 管理控制類闪湾,擁有圖片下載及緩存功能
KingfisherOptionsInfo.swift
枚舉KingfisherOptionsInfoItem 配置 Kingfisher 行為的參數(shù),包括 是否自定義緩存對象 是否自定義下載器 是否過渡動畫 是否設(shè)置下載低優(yōu)先級 是否強(qiáng)制刷新 是否僅獲取緩存圖片 是否僅緩存至內(nèi)存冻璃、是否允許圖像后臺解碼等設(shè)置响谓。
Filter.swift
圖像過濾器
Resource.swift
記錄了圖片的下載地址和緩存Key损合。
Kingfisher.swift
添加KingfisherCompatible通用協(xié)議 kf新屬性
Extension
ImageView+Kingfisher.swift
UIButton+Kingfisher.swift
NSButton+Kingfisher
對 UIImageView
UIButton
NSButton
進(jìn)行了拓展 主要用于提供 Kingfisher 的外部接口省艳。
Helpers
String+MD5.swift
負(fù)責(zé)圖片緩存時(shí)對文件名進(jìn)行MD5加密操作。
Box.swift
一個(gè)簡單泛型類
ThreadHelper.swift
中的 dispatch_async_safely_main_queue
函數(shù)接受一個(gè)閉包 利用 NSThread.isMainThread 判斷并將其放置在主線程中執(zhí)行
二嫁审、Kingfisher.swift
主要文件ImageView+Kingfisher
,KingfisherManager
,ImageCache
,ImageDownloader
,廢話不多說直接代碼學(xué)習(xí)
運(yùn)行demo 下面有這么一段代碼:
let url = URL(string:"https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")!
(cell as! CollectionViewCell).cellImageView.kf.setImage(with: url,
placeholder: nil,
options: [.transition(.fade(1))],
progressBlock: { receivedSize, totalSize in
print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
},
completionHandler: { image, error, cacheType, imageURL in
print("\(indexPath.row + 1): Finished")
})
首先調(diào)用的UIImageView
的kf
屬性 之前是調(diào)用UIImageView
的Extension
中的kf_setImage
,現(xiàn)已棄用 那kf
屬性是如何實(shí)現(xiàn)的跋炕?
下面是Kingfisher.swift
源碼
- 自定義了不同平臺下的一些類型別名 swift中的typealias 相當(dāng)于OC中的typedef
#if os(macOS)
import AppKit
public typealias Image = NSImage
public typealias Color = NSColor
public typealias ImageView = NSImageView
typealias Button = NSButton
#else
import UIKit
public typealias Image = UIImage
public typealias Color = UIColor
#if !os(watchOS)
public typealias ImageView = UIImageView
typealias Button = UIButton
#endif
#endif
- 申明了泛型類Kingfisher 實(shí)現(xiàn)了一個(gè)簡單構(gòu)造器,其中上面的cellImageView就是base屬性
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
- 申明KingfisherCompatible協(xié)議 有一個(gè)可讀屬性kf 其類型是關(guān)聯(lián)類型
/**
A type that has Kingfisher extensions.
*/
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
- KingfisherCompatible協(xié)議的實(shí)現(xiàn) 屬性kf關(guān)聯(lián)Kingfisher類型 返回一個(gè)Kingfisher實(shí)例 base 參數(shù)就是傳入的self
public extension KingfisherCompatible {
public var kf: Kingfisher<Self> {
get { return Kingfisher(self) }
}
}
- Image ImageView Button 遵守 KingfisherCompatible 協(xié)議 所以上邊的self參數(shù)就是遵守了協(xié)議的類型 因此base屬性即cellImageView
extension Image: KingfisherCompatible { }
#if !os(watchOS)
extension ImageView: KingfisherCompatible {}
extension Button: KingfisherCompatible { }
#endif
三律适、ImageView+Kingfisher
現(xiàn)在來說說setImage
這個(gè)方法的實(shí)現(xiàn) 這個(gè)方法是在Kingfisher
的Extension 中實(shí)現(xiàn) 并且要求Base
屬于UIImageView
類型 即where Base: ImageView
由于kf
屬性關(guān)聯(lián)了Kingfisher
所以可以調(diào)用(cell as! CollectionViewCell).cellImageView.kf.setImage
Extensions目錄下的三個(gè)文件都是類似實(shí)現(xiàn)的 這里就以ImageView+Kingfisher.swift為例
下面方法是外部使用Kingfisher最頻繁也是最重要的方法
第一個(gè)參數(shù)Resource
是一個(gè)URL遵守的Protocol辐烂,一般傳入圖片的URL遏插,不可為空
第二個(gè)參數(shù)placeholder
是一個(gè)默認(rèn)的占位圖,可為空
第三個(gè)參數(shù)KingfisherOptionsInfo
是個(gè)枚舉數(shù)組纠修,配置Kingfisher下載圖片的一些操作行為
第四個(gè)參數(shù)DownloadProgressBlock
是個(gè)下載進(jìn)度閉包胳嘲,可以用于更新下載UI
第五個(gè)參數(shù)completionHandler
是個(gè)下載完成閉包,閉包參數(shù)包含圖片扣草,錯(cuò)誤了牛,緩存類型,URL 信息
extension Kingfisher where Base: ImageView {
/**
Set an image with a resource, a placeholder image, options, progress handler and completion handler.
- parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
- parameter placeholder: A placeholder image when retrieving the image at URL.
- parameter options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
- parameter progressBlock: Called when the image downloading progress gets updated.
- parameter completionHandler: Called when the image retrieved and set.
- returns: A task represents the retrieving process.
- note: Both the `progressBlock` and `completionHandler` will be invoked in main thread.
The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
*/
@discardableResult 忽略返回值警告
public func setImage(with resource: Resource?,
placeholder: Image? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
{
當(dāng)傳入的resource為空時(shí) 使用guard語句提前退出 Resource是一個(gè)協(xié)議 URL遵守此協(xié)議 Resource有兩個(gè)屬性 cacheKey和downloadURL
guard let resource = resource else {
base.image = placeholder
completionHandler?(nil, nil, .none, nil)
return .empty
}
圖片加載過程中是否顯示placeholder
var options = options ?? KingfisherEmptyOptionsInfo
if !options.keepCurrentImageWhileLoading {
base.image = placeholder
}
如果indicator存在辰妙,開啟轉(zhuǎn)圈動畫 indicator 通過屬性關(guān)聯(lián)存取
let maybeIndicator = indicator
maybeIndicator?.startAnimatingView()
關(guān)聯(lián)屬性綁定下載的URL
setWebURL(resource.downloadURL)
默認(rèn)開啟加載所有GIF圖片數(shù)據(jù)鹰祸,顯示GIF 動態(tài)圖片
if base.shouldPreloadAllGIF() {
options.append(.preloadAllGIFData)
}
調(diào)用KingfisherManager的方法來獲取圖片
let task = KingfisherManager.shared.retrieveImage(
with: resource,
options: options,
progressBlock: { receivedSize, totalSize in
下載進(jìn)度回調(diào)
if let progressBlock = progressBlock {
progressBlock(receivedSize, totalSize)
}
},
completionHandler: {[weak base] image, error, cacheType, imageURL in
確保線程安全
DispatchQueue.main.safeAsync {
確保返回的圖片與URL對應(yīng)一致
guard let strongBase = base, imageURL == self.webURL else {
return
}
self.setImageTask(nil)
沒有圖片返回停止動畫返回錯(cuò)誤
guard let image = image else {
maybeIndicator?.stopAnimatingView()
completionHandler?(nil, error, cacheType, imageURL)
return
}
是否需要過渡動畫 transitionItem 為 options中第一個(gè).transition
需要過渡動畫需要滿足以下情況
1.transitionItem存在且不為.transition(.none)
2.options.forceTransition存在 或者 cacheType == .none
guard let transitionItem = options.firstMatchIgnoringAssociatedValue(.transition(.none)),
case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else
{
maybeIndicator?.stopAnimatingView()
strongBase.image = image
completionHandler?(image, error, cacheType, imageURL)
return
}
過渡動畫
#if !os(macOS)
UIView.transition(with: strongBase, duration: 0.0, options: [],
animations: { maybeIndicator?.stopAnimatingView() },
completion: { _ in
UIView.transition(with: strongBase, duration: transition.duration,
options: [transition.animationOptions, .allowUserInteraction],
animations: {
// Set image property in the animation.
設(shè)置圖片,如果是自定義動畫 在定義動畫回調(diào)中設(shè)置圖片密浑,代碼在ImageTransition.swift
transition.animations?(strongBase, image)
},
completion: { finished in
動畫結(jié)束回調(diào)
transition.completion?(finished)
completionHandler?(image, error, cacheType, imageURL)
})
})
#endif
}
})
setImageTask(task)
return task
}
/**
Cancel the image download task bounded to the image view if it is running.
Nothing will happen if the downloading has already finished.
*/
取消下載
public func cancelDownloadTask() {
imageTask?.downloadTask?.cancel()
}
}
ImageView+Kingfisher
中的WebUR
indicatorType
indicator
imageTask
屬性均使用屬性關(guān)聯(lián)技術(shù)實(shí)現(xiàn)數(shù)據(jù)的存取
四 蛙婴、KingfisherManager
該類是Kingfisher
唯一的一個(gè)管理調(diào)度類。這個(gè)類有下載和緩存兩大功能模塊 主要包含了兩個(gè)屬性 兩個(gè)方法
public var cache: ImageCache
圖片緩存屬性
public var downloader: ImageDownloader
圖片下載屬性
func downloadAndCacheImage
下載并且緩存圖片方法
func tryToRetrieveImageFromCache
獲取緩存圖片
在ImageView+Kingfisher
中最后圖片的獲取就是由KingfisherManager
的單例實(shí)現(xiàn)的retrieveImage
- 外部調(diào)用獲取圖片方法
func retrieveImage(with resource: Resource,
options: KingfisherOptionsInfo?,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?) -> RetrieveImageTask{
let task = RetrieveImageTask()
if let options = options, options.forceRefresh {
強(qiáng)制刷新 從網(wǎng)絡(luò)獲取圖片
_ = 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
}
- 下載并且緩存圖片的方法
func downloadAndCacheImage(with url: URL,
forKey key: String,
retrieveImageTask: RetrieveImageTask,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?,
options: KingfisherOptionsInfo?) -> RetrieveImageDownloadTask?
{
獲取下載器 并開啟下載
let options = options ?? KingfisherEmptyOptionsInfo
let downloader = options.downloader
return downloader.downloadImage(with: url, retrieveImageTask: retrieveImageTask, options: options,
progressBlock: { receivedSize, totalSize in
progressBlock?(receivedSize, totalSize)
},
completionHandler: { image, error, imageURL, originalData in
let targetCache = options.targetCache
if let error = error, error.code == KingfisherError.notModified.rawValue {
// Not modified. Try to find the image from cache.
// (The image should be in cache. It should be guaranteed by the framework users.)
如果有錯(cuò)誤并且沒有修改過URL 返回緩存圖片
targetCache.retrieveImage(forKey: key, options: options, completionHandler: { (cacheImage, cacheType) -> () in
completionHandler?(cacheImage, nil, cacheType, url)
})
return
}
緩存圖片
if let image = image, let originalData = originalData {
targetCache.store(image,
original: originalData,
forKey: key,
processorIdentifier:options.processor.identifier,
cacheSerializer: options.cacheSerializer,
toDisk: !options.cacheMemoryOnly,
completionHandler: nil)
}
completionHandler?(image, error, .none, url)
})
}
- 優(yōu)先從緩存獲取圖片尔破,如緩存中沒有街图,在從網(wǎng)絡(luò)獲取圖片
func tryToRetrieveImageFromCache(forKey key: String,
with url: URL,
retrieveImageTask: RetrieveImageTask,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?,
options: KingfisherOptionsInfo?)
{
打破下面diskTask內(nèi)部閉包保持的循環(huán)引用,完成之后取消磁盤任務(wù)引用懒构,避免循環(huán)引用台夺,釋放內(nèi)存
let diskTaskCompletionHandler: CompletionHandler = { (image, error, cacheType, imageURL) -> () in
// Break retain cycle created inside diskTask closure below
retrieveImageTask.diskRetrieveTask = nil
completionHandler?(image, error, cacheType, imageURL)
}
let targetCache = options?.targetCache ?? cache
let diskTask = targetCache.retrieveImage(forKey: key, options: options,
completionHandler: { image, cacheType in
if image != nil {
成功返回圖片
diskTaskCompletionHandler(image, nil, cacheType, url)
} else if let options = options, options.onlyFromCache {
返回失敗 并且設(shè)置只從緩存獲取圖片 返回沒有緩存錯(cuò)誤
let error = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notCached.rawValue, userInfo: nil)
diskTaskCompletionHandler(nil, error, .none, url)
} else {
返回失敗 再從網(wǎng)絡(luò)下載圖片
self.downloadAndCacheImage(
with: url,
forKey: key,
retrieveImageTask: retrieveImageTask,
progressBlock: progressBlock,
completionHandler: diskTaskCompletionHandler,
options: options)
}
}
)
retrieveImageTask.diskRetrieveTask = diskTask
}
五、 KingfisherOptionsInfo
上面代碼多次用到options
這個(gè)參數(shù)痴脾,它的參數(shù)類型是KingfisherOptionsInfo
是一個(gè)類型別名
public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]
KingfisherOptionsInfoItem
是一個(gè)枚舉 配置 Kingfisher所有功能行為 下面是詳細(xì)中文注釋
public enum KingfisherOptionsInfoItem {
這個(gè)成員的關(guān)聯(lián)值是一個(gè)ImageCache對象颤介。 Kingfisher使用指定的緩存對象處理 相關(guān)業(yè)務(wù),包括試圖檢索緩存圖像和存儲下載的圖片。
case targetCache(ImageCache)
這個(gè)成員的關(guān)聯(lián)值應(yīng)該是一個(gè)ImageDownloader對象赞赖。Kingfisher將使用這個(gè)下載器下載的圖片滚朵。
case downloader(ImageDownloader)
如果從網(wǎng)絡(luò)下載的圖片 Kingfisher將使用“ImageTransition這個(gè)枚舉動畫。從內(nèi)存或磁盤緩存時(shí)默認(rèn)過渡不會發(fā)生前域。如果需要,設(shè)置ForceTransition
case transition(ImageTransition)
有關(guān)“浮動”值將被設(shè)置為圖像下載任務(wù)的優(yōu)先級辕近。值在0.0 ~ 1.0之間。如果沒有設(shè)置這個(gè)選項(xiàng),默認(rèn)值(“NSURLSessionTaskPriorityDefault”)將被使用匿垄。
case downloadPriority(Float)
如果設(shè)置,將忽略緩存,開啟一個(gè)下載任務(wù)的資源
case forceRefresh
如果設(shè)置 即使緩存的圖片也將開啟過渡動畫
case forceTransition
如果設(shè)置移宅,Kingfisher只會在內(nèi)存中緩存值而不是磁盤
case cacheMemoryOnly
如果設(shè)置 Kingfisher只會從緩存中加載圖片
case onlyFromCache
在使用之前在后臺線程解碼圖像
case backgroundDecode
當(dāng)從緩存檢索圖像時(shí) 這個(gè)成員的關(guān)聯(lián)值將被用作目標(biāo)隊(duì)列的調(diào)度時(shí)回調(diào)。如果沒 有設(shè)置, Kingfisher將使用主要quese回調(diào)
case callbackDispatchQueue(DispatchQueue?)
將檢索到的圖片數(shù)據(jù)轉(zhuǎn)換成一個(gè)圖時(shí) 這個(gè)成員變量將被用作圖片縮放因子椿疗。圖像分辨率,而不是屏幕尺寸漏峰。你可能處理時(shí)需要指定正確的縮放因子@2x或@3x Retina圖像。
case scaleFactor(CGFloat)
是否所有的GIF應(yīng)該加載數(shù)據(jù)届榄。默認(rèn)false浅乔,只顯示GIF中第一張圖片。如果true,所有的GIF數(shù)據(jù)將被加載到內(nèi)存中進(jìn)行解碼。這個(gè)選項(xiàng)主要是用于內(nèi)部的兼容性靖苇。你不應(yīng)該把直接設(shè)置它席噩。“AnimatedImageView”不會預(yù)加載所有數(shù)據(jù),而一個(gè)正常的圖像視圖(“UIImageView”或“NSImageView”)將加載所有數(shù)據(jù)贤壁。選擇使用相應(yīng)的圖像視圖類型而不是設(shè)置這個(gè)選項(xiàng)悼枢。
case preloadAllGIFData
發(fā)送請求之前用于改變請求。這是最后的機(jī)會你可以修改請求脾拆。您可以修改請求一些定制的目的,如添加身份驗(yàn)證令牌頭,進(jìn)行基本的HTTP身份驗(yàn)證或類似的url映射萧芙。原始請求默認(rèn)情況下將沒有任何修改
case requestModifier(ImageDownloadRequestModifier)
下載完成時(shí),處理器會將下載的數(shù)據(jù)轉(zhuǎn)換為一個(gè)圖像。如果緩存連接到下載器(當(dāng)你正在使用KingfisherManager或圖像擴(kuò)展方法),轉(zhuǎn)換后的圖像也將被緩存
case processor(ImageProcessor)
提供一個(gè)CacheSerializer 可用于圖像對象序列化成圖像數(shù)據(jù)存儲到磁盤緩存和從磁盤緩存將圖片數(shù)據(jù)反序列化成圖像對象
case cacheSerializer(CacheSerializer)
保持現(xiàn)有的圖像同時(shí)設(shè)置另一個(gè)圖像圖像視圖假丧。通過設(shè)置這個(gè)選項(xiàng),imageview的placeholder參數(shù)將被忽略和當(dāng)前圖像保持同時(shí)加載新圖片
case keepCurrentImageWhileLoading
}
下面是自定義<== 運(yùn)算符 比較兩個(gè)KingfisherOptionsInfoItem 是否相等 相等返回true 否則返回false
precedencegroup ItemComparisonPrecedence {
associativity: none
higherThan: LogicalConjunctionPrecedence
}
infix operator <== : ItemComparisonPrecedence
// This operator returns true if two `KingfisherOptionsInfoItem` enum is the same, without considering the associated values.
func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool {
switch (lhs, rhs) {
case (.targetCache(_), .targetCache(_)): return true
case (.downloader(_), .downloader(_)): return true
case (.transition(_), .transition(_)): return true
case (.downloadPriority(_), .downloadPriority(_)): return true
case (.forceRefresh, .forceRefresh): return true
case (.forceTransition, .forceTransition): return true
case (.cacheMemoryOnly, .cacheMemoryOnly): return true
case (.onlyFromCache, .onlyFromCache): return true
case (.backgroundDecode, .backgroundDecode): return true
case (.callbackDispatchQueue(_), .callbackDispatchQueue(_)): return true
case (.scaleFactor(_), .scaleFactor(_)): return true
case (.preloadAllGIFData, .preloadAllGIFData): return true
case (.requestModifier(_), .requestModifier(_)): return true
case (.processor(_), .processor(_)): return true
case (.cacheSerializer(_), .cacheSerializer(_)): return true
case (.keepCurrentImageWhileLoading, .keepCurrentImageWhileLoading): return true
default: return false
}
}
下面是對CollectionType
的一個(gè)擴(kuò)展 返回匹配的第一個(gè)相同枚舉值 上面過渡動畫就有用到
public extension Collection where Iterator.Element == KingfisherOptionsInfoItem {
func firstMatchIgnoringAssociatedValue(_ target: Iterator.Element) -> Iterator.Element? {
return index { $0 <== target }.flatMap { self[$0] }
}
func removeAllMatchesIgnoringAssociatedValue(_ target: Iterator.Element) -> [Iterator.Element] {
return self.filter { !($0 <== target) }
}
}
在KingfisherOptionsInfo
中有很多的類似的屬性get方法 如下是關(guān)于圖片編碼的双揪,默認(rèn)返回DefaultCacheSerializer.default
。如果要自定義圖片編碼包帚,可以添加自定義CacheSerializer
到Options
數(shù)組
public var cacheSerializer: CacheSerializer {
if let item = firstMatchIgnoringAssociatedValue(.cacheSerializer(DefaultCacheSerializer.default)),
case .cacheSerializer(let cacheSerializer) = item
{
return cacheSerializer
}
return DefaultCacheSerializer.default
}
結(jié)束
至此 渔期,我們對Kingfisher對整體架構(gòu)已經(jīng)有比較清晰的認(rèn)識了 如下圖所示
由于源代碼比較多,一些注釋都寫在代碼部分渴邦,可能看起來有點(diǎn)怪 用簡書也有段時(shí)間疯趟,但這還是第一次自己寫文章 接下來我會繼續(xù)學(xué)習(xí)下載模塊和緩存模塊的過程等等 如有錯(cuò)誤,希望大家不吝指正