KingFisher类似SDWebImage,是Swift下的图片加载库。其基本用法如下:
imageView.kf.setImage(with: url)
// 带背景图片
let image = UIImage(named: "default_profile_icon")
imageView.kf.setImage(with: url, placeholder: image)
// 设置下载指示器
imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: url)
//在显示和缓存之前将下载的图像转换成圆角
let processor = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, placeholder: nil, options: [.processor(processor)])
1. 基础构成
Kingfisher提供的kf本质上是一个模板类:KingfisherWrapper
public extension KingfisherCompatible {
public var kf: KingfisherWrapper {
get { return KingfisherWrapper(self) }
set { }
}
}
extension Image: KingfisherCompatible { }
#if !os(watchOS)
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
#else
extension WKInterfaceImage: KingfisherCompatible { }
#endif
对我们来说,使用最多的是ImageView上的kf,也就是
extension KingfisherWrapper where Base: ImageView {}
定义的setImage函数
public func setImage(
with source: Source?,
placeholder: Placeholder? = nil,
options: KingfisherOptionsInfo? = nil,
progressBlock: DownloadProgressBlock? = nil,
completionHandler: ((Result) -> Void)? = nil) -> DownloadTask?
该函数虽然写的很复杂,其实本质就是根据KingfisherOptionsInfo作为配置,设置Resource和Placeholder,通过KingfisherManager.shared.retrieveImage函数下载数据的过程。
1.1 Source
Resource 是一个枚举,混一了网络:network(Resource) 与 其他图片提供者:provider(ImageDataProvider),通过扩展提供了对 Resource和ImageDataProvider的转化
public enum Source {
case network(Resource)
case provider(ImageDataProvider)
}
extension Source {
var asResource: Resource? {
guard case .network(let resource) = self else {
return nil
}
return resource
}
var asProvider: ImageDataProvider? {
guard case .provider(let provider) = self else {
return nil
}
return provider
}
}
1.1.1 Resource
Resource是一个协议,标志这图片来自网络,提供cacheKey : String,downloadURL:URL。URL实现该协议,将absoluteString作为缓存的Key。
1.1.2 ImageDataProvider
ImageDataProvider是一个协议,标志这图片来自网络,提供cacheKey : String,func data() 来缓存与生成Image。
KingFisher提供了三种默认的Provider:
LocalFileImageDataProvider, 从本地file中读取Image
Base64ImageDataProvider,从Base64中读取Image
RawImageDataProvider, 从Data中读取Image
1.2 Placeholder
Placeholder 也是一个协议,提供在ImageView上添加自身和移除自身的功能函数:
func add(to imageView: ImageView)
func remove(from imageView: ImageView)
Image和View有默认的配置:
extension Placeholder where Self: Image
extension Placeholder where Self: View
Image的默认方法是设置imageView的image为自身
View的默认方法是添加覆盖imageView的子View
1.3 KingfisherOptionsInfo
KingfisherOptionsInfos是配置的集合,而KingfisherOptionsInfoItem是一个如下的枚举
public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]
/**
Items could be added into KingfisherOptionsInfo.
*/
public enum KingfisherOptionsInfoItem {
case targetCache(ImageCache) //设置缓存器,Kingfisher用这个缓存器来缓存展示的图片
case originalCache(ImageCache) //设置缓存器,Kingfisher用这个缓存器来缓存下载的原始图片
case downloader(ImageDownloader) //设置下载器,Kingfisher用这个下载器来下载数据
case transition(ImageTransition) //设置下载完成之后的动画
case downloadPriority(Float) //0.0~1.0 设置下载优先级
case forceRefresh //忽视缓存
case fromMemoryCacheOrRefresh //先尝试从内存缓存读取,如果没有,则重新下载,不会读取磁盘缓存
case forceTransition //从缓存读取的图片也会进行动画处理
case cacheMemoryOnly //只通过内存缓存图片
case waitForCache //缓存完成之后才调用completion block
case onlyFromCache //只通过缓存读取图片,不会下载
case backgroundDecode //使用图片前线在后台线程上解码
case callbackDispatchQueue(DispatchQueue?) //设置回调在那个队列上
case scaleFactor(CGFloat) // data转成Image时的Scale
.....
}
2. KingfisherManager
KingfisherManager主要由两部分组成,ImageDownloader用于管理下载;ImageCache用于管理缓存。其主要函数即为:
func retrieveImage(
with source: Source,
options: KingfisherParsedOptionsInfo,
completionHandler: ((Result) -> Void)?) -> DownloadTask?
阅读源码之后,我们可以发现函数loadAndCacheImage负责下载及缓存图片,函数retrieveImageFromCache负责读出cache中的图片。
3. 图片下载
涉及类: ImageDownloader
这里我们只讨论Source为Resource,也就是从network上获取图片的部分。Source为ImageDataProvider的部分较为简单,就是调用ImageDataProvider的data方法获取Image。
经过抽丝剥茧,下载图片的代码如下:
let downloader = options.downloader ?? ImageDownloader.default
guard let task = downloader.downloadImage(
with: resource.downloadURL,
options: options,
completionHandler: cacheImage) else {
return nil
}
return .download(task)
3.1 ImageDownloader
ImageDownloader是图片的下载器, 本类中需要注意的成员变量有如下几个:
网络请求的抽象:
private let sessionDelegate: SessionDelegate
private var session: URLSession
open var sessionConfiguration = URLSessionConfiguration.ephemeral {
didSet {
session.invalidateAndCancel()
session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
}
}
// 自定义证书的验证逻辑
// https://www.cnblogs.com/Code-life/p/7806824.html
open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable?
图片下载的逻辑可以看这篇文章:
KingFisher源码解析-网络请求
学到的东西
1. kf 写法
在SnapKit中,view.snp是通过对View进行扩展实现的
类似snp的写法:
public var snp: ConstraintViewDSL {
return ConstraintViewDSL(view: self)
}
这种写法来为类添加一个不存在的属性
在KingFisher中,是通过泛型与协议结合的方式实现的:
/**
A type that has Kingfisher extensions.
*/
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
public extension KingfisherCompatible {
public var kf: Kingfisher {
return Kingfisher(self)
}
}
extension ImageView: KingfisherCompatible { }
extension Image: KingfisherCompatible { }
这样 imageView.kf 就等同于 let kf: Kingfisher = Kingfisher
2. 读写权限不同
fileprivate(set)
/// It will be `nil` if `indicatorType` is `.none`.
public fileprivate(set) var indicator: Indicator? {
get {
...
}
set {
...
}
}
3. Swift5 支持的Result
func cacheImage(_ result: Result)
{
switch result {
case .success(let value):
handle(value)
case .failure(let error):
handle(error)
}
}
value是ImageLoadingResult, error是KingfisherError
4. 禁止网络请求的缓存
4.1. cachePolicy
URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
4.2. URLSessionConfiguration.ephemeral
为了防止网络请求在我们不知道的情况下被缓存,可以使用ephmeral来进行请求
URLSession是一个可以响应发送或者接受HTTP请求的关键类,可以通过URLSessionConfiguration类新建URLSession实例。有以下三种方式:
URLSessionConfiguration.default
默认configuration实例创建方式,使用硬盘上持久化全局缓存、证书(credential)和cookie的存储对象
URLSessionConfiguration.ephemeral
唯一跟默认configuration不一样的是所以与会话(session)相关的数据都存储在内存中
URLSessionConfiguration.background(withIdentifier: "ConfigurationID")
让会话在后台执行上载或下载任务。即使应用程序本身被暂停或终止,传输仍将继续
5. URLRequest.httpShouldUsePipelining
通常默认情况下请求和响应是顺序的, 也就是说请求–>得到响应后,才能再次请求.
如果将HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求. 这个会很大的提高网络请求的效率,但是也可能会出问题.
因为客户端无法正确的匹配请求与响应, 所以这依赖于服务器必须保证,响应的顺序与客户端请求的顺序一致.如果服务器不能保证这一点, 那可能导致响应和请求混乱.