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

4,555 阅读4分钟

前言

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

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

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

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

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

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 的代理方法中预加载。示例代码如下:

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.

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

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

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 UIImageViewNSImageViewNSButtonUIButtonNSTextAttachmentWKInterfaceImageTVMonogramView and CPListItem to directly set an image from a URL.

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

extension KFCrossPlatformImage: KingfisherCompatible { }
extension KFCrossPlatformImageView: KingfisherCompatible { }
extension KFCrossPlatformButton: KingfisherCompatible { }
extension NSTextAttachment: KingfisherCompatible { }

Built-in transition animation when setting images.

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

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 的一层封装:

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.

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

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

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

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