Kingfisher 4.x 从使用到源码

如何使用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中,定义当BaseImageView的时候,给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存在,显示加载动画(若存在),调用KingfisherManagerretrieveImage方法,在拉取数据过程中,实时回调当前已收到的资源数据和总数据;完成后,主线程异步变化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. 新建获取图片的任务taskRetrieveImageTask类型),并读取配置optionsKingfisherOptionsInfo类型)

2. 如果配置中forceRefresh(永远从网络读取图片数据)为真,则调用本类中的downloadAndCacheImage方法,其使用ImageDownloaderdownloadImage下载图片,下载完成后,如果返回的错误是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文件夹下根据keydata创建缓存文件(如果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对应不同的ImageFetchLoadImageFetchLoad包含一个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作为loadkey,要开始进程之前要向requestModifier拿到最终的URL。可以在该方法中设置下载的timeout(超时)时长、是否使用HTTP pipelining。(使用HTTP pipelining则允许不必等到response, 就可以再次请求,会很大的提高网络请求的效率)。如果URLretrieveImageTaskmodifier有误,则在回调中显示错误信息,并返回nil。如果均无误,则调用该类的setup方法,设置并开启下载任务,fetchLoad下载任务数加一,retrieveImageTaskdownloadTask设为setup方法回调中的fetchLoaddownloadTask,后返回下载任务。 

//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(由传入的progressBlockcompletionHandler所组成)以及当前配置(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方法中,开启一个DispatchWorkItemFlagsbarrier的线程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文件定义了一些配置。包括缓存,下载优先级,是否每次重新下载图片,是否只从缓存加载图片,在发送下载请求前使用的modifierImageDownloadRequestModifier类型),下载完的图片处理器processorImageProcessor类型),在图片显示前修改图片的imageModifierImageModifier类型)等。

ImageProcessor.swift

ImageProcessor是一个协议,将下载的数据转换为图片。

DefaultImageProcessor是默认的Processor,遵循ImageProcessor协议,将输入的数据变为有效的图片,支持PNG,JPEG,GIF等格式的图片,如果有ImageDefaultImageProcessor会返回图片;有Data,就将data转为Image

BlendImageProcessor为图片添加blend模式。

ImageTransition.swift

可以通过ImageTransition.swift文件来为已下载图片显示添加动画,例如渐入、从上下左右进入等动画,也可自定义动画。


转载于:https://juejin.im/post/5b72a2b06fb9a009a44a9089

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值