如何使用Kingfisher展示图片?
官方Demo中使用setImage展示图片
// source: Kingfisher-Demo > ViewController.swift
_ = (cell as! CollectionViewCell).cellImageView.kf.setImage(with: url,
placeholder: nil,
options: [.transition(ImageTransition.fade(1))],
progressBlock: { receivedSize, totalSize in
print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
},
completionHandler: { image, error, cacheType, imageURL in
print("\(indexPath.row + 1): Finished")
})
复制代码
为什么要使用kf
?
Kingfisher.swift
KingfisherCompatible
协议有个只读属性kf
,它返回Kingfisher
。Demo中的cellImageView.kf
即为Kingfisher(ImageView)
。
//source: Kingfisher > Kingfisher.swift
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
public extension KingfisherCompatible {
public var kf: Kingfisher<Self> {
return Kingfisher(self)
}
}复制代码
kingfisher
中,对不同的种类可以进行各种不同的操作,比如对Image,你能对它设置图片、设置动画;对UIButton的不同状态设置图片;还有ImageView、WKInterfaceImage(watchOS中)。我们把这些不同的种类归位Base(泛型),它可以是Image,也可以是UIButton。不可继承的类Kingfisher
就有属性Base
,初始化时传入一个Base
。
//source: Kingfisher > Kingfisher.swift
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
复制代码
//source: Kingfisher > Kingfisher.swift
extension Image: KingfisherCompatible { }
复制代码
在Kingfisher.swift中,让ImageView
实现KingfisherCompatible
协议。
ImageView+Kingfisher.swift
在ImageView+Kingfisher.swift中,定义当Base
时ImageView
的时候,给Kingfisher
拓展方法,例如setImage
方法。
//source: Kingfisher > Extensions > ImageView+Kingfisher.swift
extension Kingfisher where Base: ImageView
public func setImage(with resource: Resource?,
placeholder: Placeholder? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
{// 代码太长,此处省略}复制代码
该setImage
方法主要做了以下事:
1. 如果resource
不存在,比如没有传入图的url
,则给一个默认的placeholder
(占位图)。
2. 如果resource
存在,显示加载动画(若存在),调用KingfisherManager
的retrieveImage
方法,在拉取数据过程中,实时回调当前已收到的资源数据和总数据;完成后,主线程异步变化UI,不再显示加载动画,没有收到image就回调错误,有收到image就将base
(此处为UIImageView
)的image
设为返回的image
。非macOS平台可设置相关动画。
KingfisherManager.swift
该类管理了Kingfisher的下载和缓存,可以使用这个类来从网络URL地址或者缓存获取图片,retrieveImage方法。
//source: Kingfisher > Core > KingfisherManager.swift
@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
}
复制代码
1. 新建获取图片的任务task
(RetrieveImageTask
类型),并读取配置options
(KingfisherOptionsInfo
类型)
2. 如果配置中forceRefresh
(永远从网络读取图片数据)为真,则调用本类中的downloadAndCacheImage
方法,其使用ImageDownloader
的downloadImage
下载图片,下载完成后,如果返回的错误是notModified
,尝试从targetCache
读取已缓存的图片;如果下载成功则使用targetCache(ImageCache)
类型的store
方法缓存图片,然后如果配置中cacheOriginalImage
为真且当前processor
不是DefaultImageProcessor
,使用originalCache
缓存图片。
3. 如果配置中forceRefresh
为假,则调用本类中的tryToRetrieveImageFromCache
方法,其先从缓存targetCache
里找,image
存在则返回;如果image
不存在,且正在使用DefaultImageProcessor
,则去下载图片;如非使用DefaultImageProcessor
,则用originalCache
去看看原始图片是否已在缓存中,如果有则不再下载,直接用processor
处理图片,使用targetCache
存储再返回。
ImageCache.swift
//source: Kingfisher > Core > ImageCache.swift
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)
{// 代码太长,此处省略}
复制代码
store
方法给memoryCache
设置key
对应的image
,如果toDisk
(写入磁盘)为真,ioQueue
异步在cache
文件夹下根据key
和data
创建缓存文件(如果cache
文件夹不存在先创建一个cache
文件夹)。主线程异步处理completionHandler
。
ImageDownloader.swift
//source: Kingfisher > Core > ImageDownloader.swiftopen class ImageDownloader {
class ImageFetchLoad {
var contents = [(callback: CallbackPair, options: KingfisherOptionsInfo)]()
var responseData = NSMutableData()
var downloadTaskCount = 0
var downloadTask: RetrieveImageDownloadTask?
var cancelSemaphore: DispatchSemaphore?
}
var fetchLoads = [URL: ImageFetchLoad]()
//其他属性省略
}复制代码
ImageDownloader
类是根据URL
从服务器下载图片的下载管理器,在fetchLoads
中,不同的URL
对应不同的ImageFetchLoad
,ImageFetchLoad
包含一个URL
中下载的内容(下载进度、处理回调、配置)、返回数据、下载任务数、下载任务、取消信号量。
//source: Kingfisher > Core > ImageDownloader.swift
@discardableResult
open func downloadImage(with url: URL,
retrieveImageTask: RetrieveImageTask? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: ImageDownloaderProgressBlock? = nil,
completionHandler: ImageDownloaderCompletionHandler? = nil) -> RetrieveImageDownloadTask?
{// 代码太长,此处省略}复制代码
downloadImage
方法中,使用URL
和配置option
下载图片。使用URL
作为load
的key
,要开始进程之前要向requestModifier
拿到最终的URL
。可以在该方法中设置下载的timeout
(超时)时长、是否使用HTTP pipelining
。(使用HTTP pipelining
则允许不必等到response
, 就可以再次请求,会很大的提高网络请求的效率)。如果URL
、retrieveImageTask
、modifier
有误,则在回调中显示错误信息,并返回nil
。如果均无误,则调用该类的setup
方法,设置并开启下载任务,fetchLoad
下载任务数加一,retrieveImageTask
的downloadTask
设为setup
方法回调中的fetchLoad
的downloadTask
,后返回下载任务。
//source: Kingfisher > Core > ImageDownloader.swift
func setup(progressBlock: ImageDownloaderProgressBlock?, with completionHandler: ImageDownloaderCompletionHandler?, for url: URL, options: KingfisherOptionsInfo?, started: @escaping ((URLSession, ImageFetchLoad) -> Void))
{// 代码太长,此处省略}复制代码
setup
方法中,如果URL
传入fetchLoad
方法返回的ImageFetchLoad
存在且当前下载的任务数为0,则【一直等待到下载失败(下载失败会在该类的callCompletionHandlerFailure
方法释放cancelSemaphore
信号量),再尝试下载(调用该方法中的prepareFetchLoad
函数)。】反之:尝试下载(调用prepareFetchLoad
函数)。
prepareFetchLoad
函数中,开启barrier
(等待写入完成后才能读取)同步线程:1.拿到url
对应的ImageFetchLoad
(若空创建新的一个); 2.并对其content
加上一条:callbackPair
(由传入的progressBlock
和completionHandler
所组成)以及当前配置(KingfisherOptionsInfoItem
);3.最后组合成的新ImageFetchLoad
替换旧的url
对应的ImageFetchLoad
。4.session
存在则开启加载url
对应图片的session
。
//source: Kingfisher > Core > ImageDownloader.swift
func fetchLoad(for url: URL) -> ImageFetchLoad? {
var fetchLoad: ImageFetchLoad?
barrierQueue.sync(flags: .barrier) { fetchLoad = fetchLoads[url] }
return fetchLoad
}
复制代码
fetchLoad
方法中,开启一个DispatchWorkItemFlags
为barrier
的线程barrierQueue
同步执行以下操作:根据url
拿到fetchLoads
里对应的fetchLoad
,并返回fetchLoad
。
//source: Kingfisher > Core > ImageDownloader.swiftfunc urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let url = task.originalRequest?.url else {
return
}
if let downloader = downloadHolder {
downloader.delegate?.imageDownloader(downloader, didFinishDownloadingImageForURL: url, with: task.response, error: error)
}
guard error == nil else {
callCompletionHandlerFailure(error: error!, url: url)
return
}
processImage(for: task, url: url)
}
private func processImage(for task: URLSessionTask, url: URL)
{// 代码太长,此处省略}复制代码
urlSession(_:task:didCompleteWithError)
方法中,下载失败则使用该类callCompletionHandlerFailure
方法处理失败结果,下载成功则使用processImage(for:url:)
方法处理数据并执行回调,缓存图片数据并刷新界面。开启异步processQueue
(任务并行运行),使用fetchload
中的responseData
得到data
。若downloader
存在delegate,
可在创建UIImage
之前对从url
成功下载的raw image data
再次处理,例如解密(decrypting)或验证(verification)得到data
。缓存处理过的图片数据,如果使用相同的processor
,就不用重复处理该图了。
KingfisherOptionsInfo.swift
KingfisherOptionsInfo.swift文件定义了一些配置。包括缓存,下载优先级,是否每次重新下载图片,是否只从缓存加载图片,在发送下载请求前使用的modifier
(ImageDownloadRequestModifier
类型),下载完的图片处理器processor
(ImageProcessor
类型),在图片显示前修改图片的imageModifier
(ImageModifier
类型)等。
ImageProcessor.swift
ImageProcessor
是一个协议,将下载的数据转换为图片。
DefaultImageProcessor
是默认的Processor
,遵循ImageProcessor
协议,将输入的数据变为有效的图片,支持PNG,JPEG,GIF等格式的图片,如果有Image
,DefaultImageProcessor
会返回图片;有Data
,就将data
转为Image
。
BlendImageProcessor
为图片添加blend
模式。
ImageTransition.swift
可以通过ImageTransition.swift文件来为已下载图片显示添加动画,例如渐入、从上下左右进入等动画,也可自定义动画。