Kingfisher 源码解析系列,由于水平有限,哪里有错,肯请不吝赐教
1. 基本使用
1.1 通过 Resource 设置图片
Kingfisher 中内置的 ImageResource 和 URL 实现了 Resource 协议,ImageResource 和 URL 的区别是 ImageResource 可自定义 cacheKey。
1 | let url = URL(string: "https://test/image.jpg")! |
2 | imageView.kf.setImage(with: url) |
1 | let imageResource = ImageResource(downloadURL: url, cacheKey: "custom_cache_key") |
2 | imageView.kf.setImage(with: imageResource) |
1.2 通过 ImageDataProvider 设置图片
Kingfisher 内置了 LocalFileImageDataProvider,Base64ImageDataProvider,RawImageDataProvider 三种 ImageDataProvider。
- LocalFileImageDataProvider
1 | let fileUrl = Bundle.main.url(forResource: "image", withExtension: "jpg")! |
2 | let imageDataProvider = LocalFileImageDataProvider(fileURL: fileUrl) |
3 | imageView.kf.setImage(with: imageDataProvider) |
1 | let base64String = "...." |
2 | let base64ImageDataProvider = Base64ImageDataProvider(base64String: base64String, cacheKey: "base64_cache_key") |
3 | imageView.kf.setImage(with: base64ImageDataProvider) |
1 | let data = Data() |
2 | let dataImageDataProvider = RawImageDataProvider(data: data, cacheKey: "data_cache_key") |
3 | imageView.kf.setImage(with: dataImageDataProvider) |
1 | //定义 |
2 | public struct FileNameImageDataProvider : ImageDataProvider { |
3 | public let cacheKey: String |
4 | public let fileName: String |
5 | public init(fileName: String, cacheKey: String? = nil) { |
6 | self.fileName = fileName |
7 | self.cacheKey = cacheKey ?? fileName |
8 | } |
9 |
|
10 | public func data(handler: @escaping (Result<Data, Error>) -> Void) { |
11 | if let fileURL = Bundle.main.url(forResource: fileName, withExtension: "") { |
12 | handler(Result(catching: { try Data(contentsOf: fileURL) })) |
13 | }else { |
14 | let error = NSError(domain: "文件不存在", code: -1, userInfo: ["fileName":fileName]) |
15 | handler(.failure(error)) |
16 | } |
17 | } |
18 | } |
19 | //使用 |
20 | let fileNameImageDataProvider = FileNameImageDataProvider(fileName: "image.jpg") |
21 | imageView.kf.setImage(with: fileNameImageDataProvider) |
1.3 展示 placeholder
- 使用 UIImage 设置 placeholder
1 | let placeholderImage = UIImage(named: "placeholder.png") |
2 | imageView.kf.setImage(with: url, placeholder: placeholderImage) |
- 通过自定义 View 设置 placeholder
1 | // 定义 |
2 | // 需要使自定义View遵循Placeholder协议 |
3 | // 可以什么都不实现,是因为当Placeholder为UIview的时候有默认实现 |
4 | class PlaceholderView: UIView, Placeholder { |
5 | } |
6 | // 使用 |
7 | let placeholderView = PlaceholderView() |
8 | imageView.kf.setImage(with: url, placeholder: placeholderView) |
1.4 加载 GIF 图
1 | let url = URL(string: "https://test/image.gif")! |
2 | imageView.kf.setImage(with: url) |
- 通过 AnimatedImageView 加载 GIF 图
1 | let url = URL(string: "https://test/image.gif")! |
2 | animatedImageView.kf.setImage(with: url) |
上面二者的区别请参考Kingfisher 源码解析之加载动图
1.5 设置指示器
1 | imageView.kf.indicatorType = .none |
- 使用 UIActivityIndicatorView 作为指示器
1 | imageView.kf.indicatorType = .activity |
1 | let path = Bundle.main.path(forResource: "loader", ofType: "gif")! |
2 | let data = try! Data(contentsOf: URL(fileURLWithPath: path)) |
3 | imageView.kf.indicatorType = .image(imageData: data) |
1 | // 定义 |
2 | struct CustomIndicator: Indicator { |
3 | let view: UIView = UIView() |
4 | func startAnimatingView() { view.isHidden = false } |
5 | func stopAnimatingView() { view.isHidden = true } |
6 | init() { |
7 | view.backgroundColor = .red |
8 | } |
9 | } |
10 | // 使用 |
11 | let indicator = CustomIndicator() |
12 | imageView.kf.indicatorType = .custom(indicator: indicator) |
1.6 设置 transition
transition 用于图片加载完成之后的展示动画,有以下类型
1 | public enum ImageTransition { |
2 | // 无动画 |
3 | case none |
4 | // 相当于UIView.AnimationOptions.transitionCrossDissolve |
5 | case fade(TimeInterval) |
6 | // 相当于UIView.AnimationOptions.transitionFlipFromLeft |
7 | case flipFromLeft(TimeInterval) |
8 | // 相当于UIView.AnimationOptions.transitionFlipFromRight |
9 | case flipFromRight(TimeInterval) |
10 | // 相当于UIView.AnimationOptions.transitionFlipFromTop |
11 | case flipFromTop(TimeInterval) |
12 | // 相当于UIView.AnimationOptions.transitionFlipFromBottom |
13 | case flipFromBottom(TimeInterval) |
14 | // 自定义动画 |
15 | case custom(duration: TimeInterval, |
16 | options: UIView.AnimationOptions, |
17 | animations: ((UIImageView, UIImage) -> Void)?, |
18 | completion: ((Bool) -> Void)?) |
19 | } |
使用方式
1 | imageView.kf.setImage(with: url, options: [.transition(.fade(0.2))]) |
2. Processor
2.1 DefaultImageProcessor
将下载的数据转换为相应的 UIImage。支持 PNG,JPEG 和 GIF 格式。
2.2 BlendImageProcessor
修改图片的混合模式(这里不知道这么描述对不对),核心实现如下
- 首先利用 DefaultImageProcessor 把 Data 转成 image,然后去绘制
- 获取上下文
- 为上下文填充背景色
- 调用 image.draw 函数设置混合模式
- 从上下文中获取图片为 processedImage
- 释放上下文,并返回 processedImage
1 | let image = 处理之前的图片 |
2 | UIGraphicsBeginImageContextWithOptions(size, false, scale) |
3 | let context = UIGraphicsGetCurrentContext() |
4 | let rect = CGRect(origin: .zero, size: size) |
5 | backgroundColor.setFill() |
6 | UIRectFill(rect) |
7 | image.draw(in: rect, blendMode: blendMode, alpha: alpha) |
8 | let cgImage = context.makeImage() |
9 | let processedImage = UIImage(cgImage: cgImage, scale: scale, orientation: image.orientation) |
10 | UIGraphicsEndImageContext() |
11 | return processedImage |
2.3 OverlayImageProcessor
在 image 上添加一层覆盖,其本质也是混合模式,逻辑大致同上
2.4 BlurImageProcessor
给图片添加高斯模糊,用 vimage 实现
2.5 RoundCornerImageProcessor
给图片添加圆角,支持四个角进行相互组合,使用方式如下
1 | // 设置四个角的圆角 |
2 | imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20))]) |
3 | // 给最上角和右下角设置圆角 |
4 | imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20 |
5 | ,roundingCorners: [.topLeft, .bottomRight]))]) |
实现方式:利用贝塞尔曲线设置一下带圆角的圆角矩形,然后对图片进行裁剪
1 | let path = UIBezierPath( |
2 | roundedRect: rect, |
3 | byRoundingCorners: corners.uiRectCorner,//此参数表示是哪个圆角 |
4 | cornerRadii: CGSize(width: radius, height: radius) |
5 | ) |
6 | context.addPath(path.cgPath) |
7 | context.clip() |
8 | image.draw(in: rect) |
2.6 TintImageProcessor
用颜色给图像主色,实现方式是利用 CoreImage 中的 CIFilter,使用了这 2 个CIFilter(name: "CIConstantColorGenerator")和CIFilter(name: "CISourceOverCompositing")
2.7 ColorControlsProcessor
修改图片的对比度,曝光度,亮度,饱和度,实现方式是利用 CoreImage 中的 CIFilter,使用了这 2 个CIColorControls和CIExposureAdjust
2.8 BlackWhiteProcessor
使图像灰度化,是 ColorControlsProcessor 的特例
2.9 CroppingImageProcessor
对图片进行裁剪
2.10 DownsamplingImageProcessor
对图片下采样,一般在较小的 imageView 展示较大的高清图
核心实现:
1 | public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? { |
2 | let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary |
3 | guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else { |
4 | return nil |
5 | } |
6 |
|
7 | let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale |
8 | let downsampleOptions = [ |
9 | kCGImageSourceCreateThumbnailFromImageAlways: true, |
10 | kCGImageSourceShouldCacheImmediately: true, |
11 | kCGImageSourceCreateThumbnailWithTransform: true, |
12 | kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary |
13 | guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { |
14 | return nil |
15 | } |
16 | return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil) |
17 | } |
2.11 GeneralProcessor
用于组合多个已有的 Processor,使用方式如下,最终都会转成 GeneralProcessor
1 | // 使用方式1 |
2 | let processor1 = BlurImageProcessor(blurRadius: 5) |
3 | let processor2 = RoundCornerImageProcessor(cornerRadius: 20) |
4 | let generalProcessor = GeneralProcessor(identifier: "123") { (item, options) -> KFCrossPlatformImage? in |
5 | if let image = processor1.process(item: item, options: options) { |
6 | return processor2.process(item: .image(image), options: options) |
7 | } |
8 | return nil |
9 | } |
10 | // 使用方式2,此方法是Processor的扩展 |
11 | let generalProcessor = BlurImageProcessor(blurRadius: 5).append(RoundCornerImageProcessor(cornerRadius: 20)_ |
12 | // 使用方式3,自定义的操作符,调用了append方法 |
13 | let generalProcessor = BlurImageProcessor(blurRadius: 5) |> RoundCornerImageProcessor(cornerRadius: 20) |
2.12 自定义 Processor
参考Kingfisher 源码解析之 Processor 和 CacheSerializer
3 缓存
3.1 使用自定义的 cacheKey
通常情况下,会直接通过 URL 去加载图片,这个时候 cacheKey 是 URL.absoluteString,也可使用 ImageResource 自定义 cacheKey
3.2 通过 cacheKey 判断是否缓存,以及缓存的类型
cacheType 是一个枚举,有三个 case:.none 未缓存,.memory 存在内存缓存,.disk 存在磁盘缓存。
需要说明的是 cacheKey+processor.identifier 才是缓存的唯一标识符,只是 DefaultImageProcessor 的 identifier 为空字符串,若是在加载的时候指定了非 DefaultImageProcessor 的 Processor,则在查找的时候需要指定 processorIdentifier
1 | let cache = ImageCache.default |
2 | let isCached = cache.isCached(forKey: cacheKey) |
3 | let cacheType = cache.imageCachedType(forKey: cacheKey) |
4 | // 若是指定了Processor,可使用此方法查找缓存 |
5 | cache.isCached(forKey: cacheKey, processorIdentifier: processor.identifier) |
3.3 通过 cacheKey,从缓存中获取图片
1 | cache.retrieveImage(forKey: "cacheKey") { result in |
2 | switch result { |
3 | case .success(let value): |
4 | print(value.cacheType) |
5 | print(value.image) |
6 | case .failure(let error): |
7 | print(error) |
8 | } |
9 | } |
3.4 设置缓存的配置
3.4.1 设置内存缓存的容量限制(默认值设置物理内存的四分之一)
1 | cache.memoryStorage.config.totalCostLimit = 100 * 1024 * 1024 |
3.4.2 设置内存缓存的个数限制
1 | cache.memoryStorage.config.countLimit = 150 |
3.4.3 设置内存缓存的过期时间(默认值是 300 秒)
1 | cache.memoryStorage.config.expiration = .seconds(300) |
也可指定某一个图片的内存缓存
1 | imageView.kf.setImage(with: url, options:[.memoryCacheExpiration(.never)]) |
3.4.4 设置内存缓存的过期时间更新策略
更新策略是一个枚举,有三个 case,.none 过期时间不更新,.cacheTime 在当前时间上加上过期时间,.expirationTime(_ expiration: StorageExpiration)过期时间更新为指定多久之后过期。默认值是.cacheTime,使用方式如下
1 | imageView.kf.setImage(with: url, options:[.memoryCacheAccessExtendingExpiration(.cacheTime)]) |
3.4.5 设置内存缓存清除过期内存的时间间隔(此值是不可变的,只可在初始化时赋值)
1 | cache.memoryStorage.config.cleanInterval = 120 |
3.4.6 设置磁盘缓存的容量
1 | cache.diskStorage.config.sizeLimit = = 500 * 1024 * 1024 |
3.4.7 设置磁盘缓存的过期时间和过期时间更新策略
同内存缓存
3.5 手动的缓存图片
1 | // 普通缓存 |
2 | let image: UIImage = //... |
3 | cache.store(image, forKey: cacheKey) |
4 |
|
5 | // 缓存原始数据 |
6 | let data: Data = //... |
7 | let image: UIImage = //... |
8 | cache.store(image, original: data, forKey: cacheKey) |
3.6 清除缓存
3.6.1 删除指定的缓存
1 | forKey: cacheKey, |
2 | processorIdentifier: processor.identifier, |
3 | fromMemory: false,//是否才能够内存缓存中删除 |
4 | fromDisk: true //是否从磁盘缓存中删除){} |
3.6.2 清空内存缓存,清空过期的内存缓存
1 | // 清空内存缓存 |
2 | cache.clearMemoryCache() |
3 | // 清空过期的内存缓存 |
4 | cache.cleanExpiredMemoryCache() |
3.6.3 清空磁盘缓存,清空过期的磁盘缓存和超过磁盘容量限制的缓存
1 | // 清空磁盘缓存 |
2 | cache.clearDiskCache() |
3 | // 清空过期的磁盘缓存和超过磁盘容量限制的缓存 |
4 | cache.cleanExpiredDiskCache() |
3.7 获取磁盘缓存大小
1 | cache.calculateDiskStorageSize() |
4. 下载
4.1 手动下载图片
1 | let downloader = ImageDownloader.default |
2 | downloader.downloadImage(with: url) { result in |
3 | switch result { |
4 | case .success(let value): |
5 | print(value.image) |
6 | case .failure(let error): |
7 | print(error) |
8 | } |
9 | } |
4.2 在发送请求之前,修改 Request
1 | // 定义一个requestModifier |
2 | let modifier = AnyModifier { request in |
3 | var r = request |
4 | r.setValue("abc", forHTTPHeaderField: "Access-Token") |
5 | return r |
6 | } |
7 | // 在手动下载时设置 |
8 | downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) { |
9 | } |
10 | // 在imageView的setImage的options里设置 |
11 | imageView.kf.setImage(with: url, options: [.requestModifier(modifier)]) |
4.3 设置超时时间
1 | downloader.downloadTimeout = 60 |
4.4 处理重定向
1 | // 定义一个重定向的处理逻辑 |
2 | let anyRedirectHandler = AnyRedirectHandler { (task, resp, req, completionHandler) in |
3 | completionHandler(req) |
4 | } |
5 | // 在手动下载时设置 |
6 | downloader.downloadImage(with: url, options: [.redirectHandler(anyRedirectHandler)]) |
7 | // 在imageView的setImage的options里设置 |
8 | imageView.kf.setImage(with: url, options: [.redirectHandler(anyRedirectHandler)]) |
4.5 取消下载
1 | // 取消手动下载 |
2 | let task = downloader.downloadImage(with: url) { result in |
3 | } |
4 | task?.cancel() |
5 |
|
6 | // 取消imageView的下载 |
7 | let task = imageView.kf.set(with: url) |
8 | task?.cancel() |
5. 预加载
使用方式如下,具体可参考Kingfisher 源码解析之 ImagePrefetcher
1 | let urls = ["https://example.com/image1.jpg", "https://example.com/image2.jpg"] |
2 | .map { URL(string: $0)! } |
3 | let prefetcher = ImagePrefetcher(urls: urls) |
4 | prefetcher.start() |
7. 一些有用的 options
- loadDiskFileSynchronously 从磁盘中加载时,是否同步的去加载
- onlyFromCache 是否只从缓存中加载
- cacheMemoryOnly 是否只使用内存缓存
- forceRefresh 是否强制刷新,若值为 true,则每次都会重新下载
- backgroundDecode 是否在子线程去解码
- …其他配置请参考Kingfisher 源码解析之 Options 解释