一拍谐、Kingfisher的架構(gòu)
閱讀他人優(yōu)秀代碼是一個(gè)提高自身代碼水平很好的方法辈双≡鹛停花了幾天的時(shí)間,看了Kingfisher的源代碼湃望,里面包含的很多知識(shí)點(diǎn)换衬,讓我受益匪淺。3.x版本相比與之前的版本证芭,一個(gè)重要的改變就是protocol的靈活運(yùn)用瞳浦,更加面向協(xié)議編程。當(dāng)然還有其他很多知識(shí)废士,比如多線程叫潦,枚舉,閉包官硝,Extension 等等應(yīng)用矗蕊。 Kingfisher中有Core,Extension氢架,Helpers 三個(gè)目錄結(jié)構(gòu) 共20個(gè)文件
Core
image.swift 文件內(nèi)部對(duì) 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ù)合成圖片對(duì)象
CacheSerializer.swift 可用于圖像對(duì)象序列化成圖像數(shù)據(jù)存儲(chǔ)到磁盤緩存和從磁盤緩存將圖片數(shù)據(jù)反序列化成圖像對(duì)象没龙。
RequestModifier.swift 下載圖像請(qǐng)求修改器。
ImageTransition.swift 過渡動(dòng)畫效果 使用UIViewAnimationOptions動(dòng)畫效果
KingfisherManager.swift Kingfisher 管理控制類缎玫,擁有圖片下載及緩存功能
KingfisherOptionsInfo.swift 枚舉KingfisherOptionsInfoItem 配置 Kingfisher 行為的參數(shù)硬纤,包括 是否自定義緩存對(duì)象 是否自定義下載器 是否過渡動(dòng)畫 是否設(shè)置下載低優(yōu)先級(jí) 是否強(qiáng)制刷新 是否僅獲取緩存圖片 是否僅緩存至內(nèi)存、是否允許圖像后臺(tái)解碼等設(shè)置赃磨。
Filter.swift 圖像過濾器
Resource.swift 記錄了圖片的下載地址和緩存Key筝家。
Kingfisher.swift 添加KingfisherCompatible通用協(xié)議 kf新屬性
Extension
ImageView+Kingfisher.swift UIButton+Kingfisher.swift NSButton+Kingfisher對(duì) UIImageView UIButton NSButton 進(jìn)行了拓展 主要用于提供 Kingfisher 的外部接口。
Helpers
String+MD5.swift 負(fù)責(zé)圖片緩存時(shí)對(duì)文件名進(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源碼
自定義了不同平臺(tái)下的一些類型別名 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)圈動(dòng)畫 indicator 通過屬性關(guān)聯(lián)存取
let maybeIndicator = indicator
maybeIndicator?.startAnimatingView()
關(guān)聯(lián)屬性綁定下載的URL
setWebURL(resource.downloadURL)
默認(rèn)開啟加載所有GIF圖片數(shù)據(jù),顯示GIF 動(dòng)態(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對(duì)應(yīng)一致
guard let strongBase = base, imageURL == self.webURL else {
return
}
self.setImageTask(nil)
沒有圖片返回停止動(dòng)畫返回錯(cuò)誤
guard let image = image else {
maybeIndicator?.stopAnimatingView()
completionHandler?(nil, error, cacheType, imageURL)
return
}
是否需要過渡動(dòng)畫 transitionItem 為 options中第一個(gè).transition
需要過渡動(dòng)畫需要滿足以下情況
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
}
過渡動(dòng)畫
#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è)置圖片盆色,如果是自定義動(dòng)畫 在定義動(dòng)畫回調(diào)中設(shè)置圖片灰蛙,代碼在ImageTransition.swift
transition.animations?(strongBase, image)
},
completion: { finished in
動(dòng)畫結(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對(duì)象案训。 Kingfisher使用指定的緩存對(duì)象處理 相關(guān)業(yè)務(wù),包括試圖檢索緩存圖像和存儲(chǔ)下載的圖片买置。
case targetCache(ImageCache)
這個(gè)成員的關(guān)聯(lián)值應(yīng)該是一個(gè)ImageDownloader對(duì)象。Kingfisher將使用這個(gè)下載器下載的圖片强霎。
case downloader(ImageDownloader)
如果從網(wǎng)絡(luò)下載的圖片 Kingfisher將使用“ImageTransition這個(gè)枚舉動(dòng)畫忿项。從內(nèi)存或磁盤緩存時(shí)默認(rèn)過渡不會(huì)發(fā)生。如果需要,設(shè)置ForceTransition
case transition(ImageTransition)
有關(guān)“浮動(dòng)”值將被設(shè)置為圖像下載任務(wù)的優(yōu)先級(jí)城舞。值在0.0 ~ 1.0之間轩触。如果沒有設(shè)置這個(gè)選項(xiàng),默認(rèn)值(“NSURLSessionTaskPriorityDefault”)將被使用。
case downloadPriority(Float)
如果設(shè)置,將忽略緩存,開啟一個(gè)下載任務(wù)的資源
case forceRefresh
如果設(shè)置 即使緩存的圖片也將開啟過渡動(dòng)畫
case forceTransition
如果設(shè)置家夺,Kingfisher只會(huì)在內(nèi)存中緩存值而不是磁盤
case cacheMemoryOnly
如果設(shè)置 Kingfisher只會(huì)從緩存中加載圖片
case onlyFromCache
在使用之前在后臺(tái)線程解碼圖像
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”不會(huì)預(yù)加載所有數(shù)據(jù),而一個(gè)正常的圖像視圖(“UIImageView”或“NSImageView”)將加載所有數(shù)據(jù)。選擇使用相應(yīng)的圖像視圖類型而不是設(shè)置這個(gè)選項(xiàng)绰精。
case preloadAllGIFData
發(fā)送請(qǐng)求之前用于改變請(qǐng)求撒璧。這是最后的機(jī)會(huì)你可以修改請(qǐng)求。您可以修改請(qǐng)求一些定制的目的,如添加身份驗(yàn)證令牌頭,進(jìn)行基本的HTTP身份驗(yàn)證或類似的url映射笨使。原始請(qǐng)求默認(rèn)情況下將沒有任何修改
case requestModifier(ImageDownloadRequestModifier)
下載完成時(shí),處理器會(huì)將下載的數(shù)據(jù)轉(zhuǎn)換為一個(gè)圖像卿樱。如果緩存連接到下載器(當(dāng)你正在使用KingfisherManager或圖像擴(kuò)展方法),轉(zhuǎn)換后的圖像也將被緩存
case processor(ImageProcessor)
提供一個(gè)CacheSerializer 可用于圖像對(duì)象序列化成圖像數(shù)據(jù)存儲(chǔ)到磁盤緩存和從磁盤緩存將圖片數(shù)據(jù)反序列化成圖像對(duì)象
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
}
}
下面是對(duì)CollectionType的一個(gè)擴(kuò)展 返回匹配的第一個(gè)相同枚舉值 上面過渡動(dòng)畫就有用到
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
}
作者:一口氣全念完
鏈接:http://www.reibang.com/p/a47fefeed7f0
來源:簡書
著作權(quán)歸作者所有靶草。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)蹄胰,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。