0%

Kingfisher源码解析之ImagePrefetcher

Kingfisher 源码解析系列,由于水平有限,哪里有错,肯请不吝赐教

ImagePrefetcher 提供了哪些功能

ImagePrefetcher 是 Kingfisher 提供预加载功能的一个类,提供了一下功能

  • start():开启预加载
  • stop():停止预加载
  • maxConcurrentDownloads:设置最大缓存并发量
  • progressBlock 和 progressSourceBlock:缓存进度的回调
  • completionHandler 和 completionSourceHandler:缓存结束的回调

ImagePrefetcher 预加载的流程图

ImagePrefetcher预加载的流程图

ImagePrefetcher 两个问题

当调用 stop()函数之后的逻辑

先来看下 stop 函数的实现,实现比较简单,在预加载的队列里异步的执行把标志位 stopped 设置为 true,并且取消当前所有未完成的下载任务,看起来很简单。

1
public func stop() {
2
    pretchQueue.async {
3
        if self.finished { return }
4
        self.stopped = true
5
        self.tasks.values.forEach { $0.cancel() }
6
    }
7
}

但是 stopped 这个标志位只在网络请求结束的回调里去判断了,这就会发生一些歧义,交给读者去判断 Kingfisher 这么做是否是合理的?当调用 stop 函数时,会出现以下几种情况以及对应的结果

  1. 调用 stop 时,已经预加载结束了,由于已经结束,会直接返回
  2. 调用 stop 时,现在已经有正在下载图片的任务了,会取消所所有请求,然后请求就会走结束的回调,在结束的回调里把剩下的未加载的数据放入到失败的数据源的数组中,调用结束回调
  3. 调用 stop 时,还没有正在下载的任务,会继续预加载数据,直到结束,或者有一个请求结束

对于情况 1 和情况 2 都是合理的,并且是绝大部分都会是情况 1 和情况 2,对于情况 3,调用 stop 时并没有真正的去停止,但是这种情况也是较少出现的。

对于 stop 方法,喵神的注释是这样的

/// Stops current downloading progress, and cancel any future prefetching activity that might be occuring.

缓存进度和缓存结束的回调为什么要各有 2 个

我第一次看代码,就想为什么要有 2 个呢?为什么这么设计呢?这里以缓存进度的回调举例,它们两个的原因是一样的。先来看下定义,

1
public typealias PrefetcherProgressBlock =
2
    ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void)
3
4
public typealias PrefetcherSourceProgressBlock =
5
    ((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void)

我们发现基本是一样的,只是回调里的参数类型不一样,一个 Resource,另一个 Source。如果你对这 2 个类型比较了解,想必你应该能猜到这么设计的原因了。

Source 是一个枚举,Kingfisher 中为 UIImage 提供数据源用的,定义如下,有 2 个 case,一个是关联了 Resource,另一个关联了 ImageDataProvider

1
public enum Source {
2
    case network(Resource)
3
    case provider(ImageDataProvider)
4
}

Resource 是一个协议,定义如下,提供数据源的真正类型之一,一般用于加载网络图片

1
public protocol Resource {
2
    var cacheKey: String { get }
3
    var downloadURL: URL { get }
4
}

ImageDataProvider 也是一个协议,定义如下,提供数据源的另一个真正类型,一般用于本地图片

1
public protocol ImageDataProvider {
2
    var cacheKey: String { get }
3
    func data(handler: @escaping (Result<Data, Error>) -> Void)
4
}

回答上面的问题,由于我们一般情况下预加载的都是网络图片,因此提供一个方便我们使用的回调,但为了覆盖到所有情况,就提供了 2 个情况的回调,这个在 ImagePrefetcher 的便利初始化方法里我们就能看出来,当使用[URL](注:在 URL 的扩展里实现了 Resource 协议)或者[Resource]初始化的时候,就使用 PrefetcherProgressBlock,当使用[Source]初始化时,就使用的 PrefetcherSourceProgressBlock