源码阅读系列之图片加载框架:Kingfisher (第三篇)

前言

在源码解析的第一篇中,我们讲解了 Kingfisher 的最主要的功能-异步加载网络图片的流程,以及如何设计支持同一个类型参数可以根据 URL 类型加载本地以及网络的资源。

第二篇中,我们讲解了它如何实现内存、磁盘双缓存以及缓存的详细配置参数。

这一篇我们接着来根据 Features 继续梳理代码。

Cancelable downloading and auto-reusing previous downloaded content to improve performance.

当我们加载列表中的图片时,可以在 UITableView 或者 UICollectionView 的代理方法中直接对 cellimageView 直接调用 setImage() 来异步加载网络资源。下面以 UICollectionView 为例子,写一下示例代码:

swift 复制代码
override func collectionView(
        _ collectionView: UICollectionView,
        cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(
        withReuseIdentifier: "collectionViewCell",
        for: indexPath) as! ImageCollectionViewCell
    let url = ImageLoader.sampleImageURLs[indexPath.row]
    cell.cellImageView.kf.setImage(with: url)
    return cell
}

这样是完全可以实现我们的需求的。但它也存在一个问题,那就是当你快速滑动的时候,当前屏幕加载的图片可能还未加载完就已经滑出屏幕了。也就是说你消耗的系统资源并不是百分百有用的,因为屏幕外的图片加载你也在执行,但这部分对用户来讲是无效的。我们可以结合 Kingfisher 的取消下载和预加载的功能来解决这个问题。

对应 UICollectionView 来说,我们可以在 didEndDisplaying 的代理方法中取消下载,在 willDisplay 的代理方法中预加载。示例代码如下:

swift 复制代码
override func collectionView(
        _ collectionView: UICollectionView,
        didEndDisplaying cell: UICollectionViewCell,
        forItemAt indexPath: IndexPath) {
    (cell as! ImageCollectionViewCell).cellImageView.kf.cancelDownloadTask()
}

override func collectionView(
    _ collectionView: UICollectionView,
    willDisplay cell: UICollectionViewCell,
    forItemAt indexPath: IndexPath) {
    let imageView = (cell as! ImageCollectionViewCell).cellImageView!
    let url = ImageLoader.sampleImageURLs[indexPath.row]
    KF.url(url).set(to: imageView)
}

Independent components. Use the downloader, caching system, and image processors separately as you need.

通过项目源码的文件分层我们也可以看出来,Kingfisher 的模块设计做的是非常好的。它的资源加载、缓存或者图片处理器都是可以单独使用的。不一定非得全套使用。

比如,在自己的项目中,经常会碰到一些数据缓存的功能。这时,你就可以单独使用它的缓存功能来实现,这样就不用自己再写一套缓存机制了。示例代码如下(以 String 为例):

swift 复制代码
extension String: CacheCostCalculable {
    public var cacheCost: Int {
        return Data(self.utf8).count
    }
}
  
let totalMemory = ProcessInfo.processInfo.physicalMemory
let costLimit = totalMemory / 4
let memoryStorage = MemoryStorage.Backend<String>(config:
    .init(totalCostLimit: (costLimit > Int.max) ? Int.max : Int(costLimit)))
memoryStorage.store(value: "测试数据", forKey: "test_key")

因为它要求存入的值遵守 CacheCostCalculable 协议以计算缓存的大小,所以我们需要给 String 的扩展中实现一下 cacheCost 计算属性。

之后的使用代码可以看出来,跟别的模块是没有什么耦合的。

Prefetching images and showing them from the cache to boost your app.

这一条跟上面的预加载是一个意思。

Extensions for UIImageView, NSImageView, NSButton, UIButton, NSTextAttachment, WKInterfaceImage, TVMonogramView and CPListItem to directly set an image from a URL.

Kingfisher 不仅仅给 UIImageView 添加了扩展属性 kf,它还给按钮、文本等控件添加了扩展属性。源码如下:

css 复制代码
extension KFCrossPlatformImage: KingfisherCompatible { }
extension KFCrossPlatformImageView: KingfisherCompatible { }
extension KFCrossPlatformButton: KingfisherCompatible { }
extension NSTextAttachment: KingfisherCompatible { }

Built-in transition animation when setting images.

Kingfisher 内部提供了很多过度动画,可以在网络资源加载完的时候展示,支持类型如下:

java 复制代码
public enum ImageTransition {
    case none
    case fade(TimeInterval)
    case flipFromLeft(TimeInterval)
    case flipFromRight(TimeInterval)
    case flipFromTop(TimeInterval)
    case flipFromBottom(TimeInterval)
    case custom(duration: TimeInterval,
                 options: UIView.AnimationOptions,
              animations: ((UIImageView, UIImage) -> Void)?,
              completion: ((Bool) -> Void)?)
}

读源码可以看到,不仅支持 5 中类型,还可以自定义自己想实现的类型。该功能对 UI 美观度要求高的 APP 开发者还是很有友好的。

动画的具体实现看源码可以知道,本质上是对 UIView.AnimationOptions 的一层封装:

swift 复制代码
var animationOptions: UIView.AnimationOptions {
    switch self {
    case .none:                         return []
    case .fade:                         return .transitionCrossDissolve
        
    case .flipFromLeft:                 return .transitionFlipFromLeft
    case .flipFromRight:                return .transitionFlipFromRight
    case .flipFromTop:                  return .transitionFlipFromTop
    case .flipFromBottom:               return .transitionFlipFromBottom
        
    case .custom(_, let options, _, _): return options
    }
}

每个动画具体的效果图,大家可以运行一下官方的 Demo 看一下。具体的逻辑在这个控制器:TransitionViewController

Customizable placeholder and indicator while loading images.

支持自定义占位图和指示器。

java 复制代码
public enum IndicatorType {
    case none
    case activity
    case image(imageData: Data)
    case custom(indicator: Indicator)
}

Extensible image processing and image format easily.

支持多种图片格式,比如:PNG、JPEG、GIF等。详情可参考 官方的 Demo。

Low Data Mode support.

支持在 Low Data Mode 的情况下,加载低分辨率的图片或者加载本地图片。

SwiftUI support.

Kingfisher 也适配了 SwiftUI,在你的 SwiftUI 项目中也可以使用它了。 示例代码如下 - KFImage

scss 复制代码
var body: some View {
    HStack(alignment: .center) {
        Spacer()
        KFImage.url(url)
            .resizable()
        Spacer()
    }.padding(.vertical, 12)
}

到这里。Kingfisher 的源码阅读就全部完成了,如果本系列文章有哪里写的不对的地方,还请读者指正。

相关推荐
中杯可乐多加冰3 天前
【AI落地应用实战】HivisionIDPhotos AI证件照制作实践指南
人工智能·掘金·金石计划
冯志浩1 个月前
Harmony Next - 多线程技术 TaskPool
harmonyos·掘金·金石计划
宇宙之一粟1 个月前
设计快速并发哈希表
后端·rust·掘金·金石计划
宇宙之一粟1 个月前
【译】Go 迭代器的乐趣
后端·go·掘金·金石计划
雨绸缪1 个月前
ABAP 的 “小技巧 ”和 “陷阱 ”以及新语法
后端·代码规范·掘金·金石计划
冯志浩2 个月前
Harmony NEXT:如何给数据库添加自定义分词
harmonyos·掘金·金石计划
中杯可乐多加冰3 个月前
【AI落地应用实战】DAMODEL深度学习平台部署+本地调用ChatGLM-6B解决方案
人工智能·掘金·金石计划
中杯可乐多加冰3 个月前
Amazon Bedrock +Amazon Step Functions实现链式提示(Prompt Chaining)
人工智能·掘金·金石计划
阿李贝斯4 个月前
el-select海量数据渲染-分页解决方案
前端·javascript·掘金·金石计划