之前在寫Objective-C的時候,都是通過使用SDWebImage來實現圖片異步下載以及緩存的。
現在的工作環境已經完全都是使用Swift了,為了工作中能夠儘量介紹OC與Swift的混合開發,就開始了解Kingfisher這個庫了。
然而在處理圖片的時候還是會思考到下面這幾個問題:
- 進入畫面,需要下載多個產品的圖片。
- 用戶上下滑動畫面,如何避免同時觸發同一個內容的下載。
- 對於下載過的內容,如果進行緩存,在下次觸發下載時,可以先從緩存中讀取。
- 對於緩存的內容是如何管理的?
於是就有了這次研究Kingfisher的動機。
Kingfisher.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#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 |
- 他通過#if os(macOS) 判斷系統類型
- typealias 為一個已存在的類別取一個別名
根據不同的系統:
為不同系統中的變量類型建立一樣的類型名稱,比如將image對應到macOS中的NSImage以及iOS中的UIImage
引入不同的framework,比如macOS中的AppKit以及iOS中的UIKit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** A type that has Kingfisher extensions. */ public protocol KingfisherCompatible { associatedtype CompatibleType var kf: CompatibleType { get } } public extension KingfisherCompatible { public var kf: Kingfisher<Self> { get { return Kingfisher(self) } } } extension Image: KingfisherCompatible { } #if !os(watchOS) extension ImageView: KingfisherCompatible { } extension Button: KingfisherCompatible { } #endif |
1 |
cell.cellImageView.kf_setImageWithURL(URL, placeholderImage: nil,..... |
1 |
cell.cellImageView.kf.setImage(with: url,........ |
從這張圖開始,接口的部分相當於對UIImageView或者
UIImageView+Kingfisher.swift
KingfisherManager
在這個類裡有多個工具函數,比如setImage(with URL)等等,不過最後都會調用KingfisherManager中下面這個方法:
1 2 3 4 5 |
public func setImage(with resource: Resource?, placeholder: Image? = nil, options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: CompletionHandler? = nil) -> RetrieveImageTask |
其中Resource包含兩個屬性cacheKey以及downloadURL,其中cacheKey就是downloadURL的字符串類型,之後會被利用作為cache存儲中的key。
而這個方法中會使用到單例KingfisherManager,這個類同時管理者downloader以及cache
1 |
let task = KingfisherManager.shared.retrieveImage |
然後通過單例會使用到這個方法:
1 2 3 4 |
public func retrieveImage(with resource: Resource, options: KingfisherOptionsInfo?, progressBlock: DownloadProgressBlock?, completionHandler: CompletionHandler?) -> RetrieveImageTask |
其中options可以對下載、緩存做一些配置上的調整。
progressBlock對應的是一個closure,用來監控下載的進度:
1 |
public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> ()) |
completionHandler是一個用來在完成任務後做處理的closure
RetrieveImageTask
KingfisherManager所調用的方法中有返回一個RetrieveImageTask
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class RetrieveImageTask { static let empty = RetrieveImageTask() // If task is canceled before the download task started (which means the `downloadTask` is nil), // the download task should not begin. var cancelledBeforeDownloadStarting: Bool = false /// The disk retrieve task in this image task. Kingfisher will try to look up in cache first. This task represent the cache search task. public var diskRetrieveTask: RetrieveImageDiskTask? /// The network retrieve task in this image task. public var downloadTask: RetrieveImageDownloadTask? /** Cancel current task. If this task does not begin or already done, do nothing. */ public func cancel() { // From Xcode 7 beta 6, the `dispatch_block_cancel` will crash at runtime. // It fixed in Xcode 7.1. // See https://github.com/onevcat/Kingfisher/issues/99 for more. if let diskRetrieveTask = diskRetrieveTask { diskRetrieveTask.cancel() } if let downloadTask = downloadTask { downloadTask.cancel() } else { cancelledBeforeDownloadStarting = true } } } |
RetrieveImageTask中包括了幾個屬性
- cancelledBeforeDownloadStarting – 用來記錄是否在下載前就呼叫了cancel方法
- diskRetrieveTask -從Disk獲取圖片的任務
- downloadTask – 從網路獲取圖片的任務
- func cancel() – diskRetrieveTask以及downloadTask的取消方法
retrieveImage方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public func retrieveImage(with resource: Resource, options: KingfisherOptionsInfo?, progressBlock: DownloadProgressBlock?, completionHandler: CompletionHandler?) -> RetrieveImageTask { let task = RetrieveImageTask() if let options = options, 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 } |
這裡可以看到Kingfisher將下載的部分(Downloader)給獨立了出來,這樣在其他地方就可以復用了(比如從緩存中讀取,如果不存在還是要進行下載)。