在 SDWebImage Source Probe: WebCache 一文中,通过最常用的 sd_setImageWithURL
方法,来分析源码。而在其中,对于图片的 download 方法,也是需要理解的重点之一。它用于处理异步下载和图片缓存的类,当然也可直接拿来使用。SDWebImageManager
这个类,为 WebCache
、 SDWebImageDownloader
和 SDImageCache
搭建了一个桥梁,使得拥有更好的协同性。而每个类负责的功能不同,又是通过该类进行了结构上的解耦。
这篇通过分析 SDWebImageManager
的 Source Code ,来深入分析一下 SD 三方库中,对于 download 方法具体实现细节。
缓存策略一览
在 WebCache 的 sd_setImageWithURL 方法中的缓存策略,调用了这个方法:
参数解释:
- url:image 对应的 url
- options:缓存策略枚举
- progress:在 download 过程中的动作,block 实现
- completed:在 download 完成后的动作,block 实现
在查看方法之前,先来查看一下缓存策略枚举(options)是如何定义的,在源码中作者已经在注释里描述了每一种枚举代表的含义,这里笔者翻译了一下:
Manage Download Image 主要流程
了解了所有的下载策略,开始阅读实现的源码。
首先,先考虑到了传递参数 url 类型为 NSString 的情况,在注释中,作者这样写道:
没有传递 NSURL ,而是使用 NSString 对象传递 url 是一个很常见的错误。由于一些奇怪的原因,Xcode 并不会抛出类型不匹配的警告。所以,我们于此允许传递 NSString 对象,并自动转换成 NSURL 从而保护该错误。
仍是为了防止参数类型错误,对 url 的类型再次进行了判断。如果非法,则赋 nil 方便后面的排查。
当 url 合法性过滤过程完成后,发现了源码中会实例化 SDWebImageCombinedOperation
这么一个对象。这是继承与 NSObject
并遵循 SDWebImageOperation
协议的一个类。
SDWebImageCombinedOperation
这个协议十分简单,仅仅声明了一个 cancel
方法。这里会自然而然的想起 NSOperation
。NSOperation
在处理事件中,会提供一个 cancel 方法可以取消当前的操作。其实,调用这个 cancel 方法,会将 SDWebImageCombinedOperation
持有的 cacheOperation
(NSOperation) 和 cancelBlock (block) 给 cancel 掉。代码如下:
这个也能体现出 SD 库的简洁接口性。大概了解了代理方法 cancel 在这里的实现,下面来看一下 SDWebImageCombinedOperation 的声明。
在了解过 SDWebImageCombinedOperation 返回我们的 download 方法中,继续阅读代码。
注意到两个问题,第一个是互斥锁 @synchronized
。我们知道,在 Foundation 框架中的 Mutable 可变基础类,都是非线程安全的。这里为了避免多线程对于 failedURLs 的篡改,所以对 failedURLs 的 getter 方法执行加锁操作。
dispatch_main_sync_safe
对于宏 dispatch_main_sync_safe
,需要读一下它的实现。
很简单,其实就是将传入的 block 同步加载到主线程中。对应的,在这个宏下面还有一个 “安全异步 block 宏”,其道理和上述宏是一样的:
下面来看最长的一部分:
在分析完冗长的代码之后,来整体把握一下 SDWebImageManager
做了哪些事情:
1. 持有 Cache 和 Downloader 协同处理
在上述代码中,经常可以看到 Cache 和 Downloader 的身影。
Cache 是图片获取的最快方式,所以在任何的 sd_set 方法上,无需立即开启 downloader 进行下载操作,而是先根据 key 从 cache 中查询。这里的 key 就是图片对应的 url 。
对于 Cache 的原理,在后续文中会做详细描述。
在 Cache 查询中无法命中后,需要启动 Downloader 进行图片下载。这个方法也是 Downloader 对外暴露的接口方法,我们可以主动调用该方法进行图片下载工作。
对于 Downloader 的原理,在后续文中会做详细描述。
2. 缓存策略与下载策略的映射
在上文中列举出了所有的缓存策略,其实在 Downloader 中有定义下载策略的枚举。具体代码后续文中会写出,这里列出一个映射图:
下载策略是根据缓存策略而决定的,而当需要重新刷新缓存策略生效的时候,会启动策略调整操作。这样保证了各个策略的协调不冲突。
3. 运用 NSMutableSet 进行 url 合法性管理
源码中有这么一行代码,用来判断 url 是否合法。
在容器类使用上,SD 使用了 NSMutableSet ,即实现了去重,又增加了查询速度。对于 NSSet 的实现,笔者还没有具体的了解,而在 Google 上的大多数资料说 NSSet 是通过 hash 来实现的。这也难怪查询元素的时候的低时间开销。
而对于每个图片的 url,SD 为了优化,将 image 对应的 url 通过关联对象添加至各个 UIImageView(UIButton),实现了更加高效的访问。所以说,在 url 的管理上,SD 的做法非常值得借鉴。
4. 时刻注意因异步带来的选择竞争问题
在整个流程中,会经常看到增加互斥量的操作。因为 SD 在不断的开启子线程进行下载操作,所以在管理 operation 或者 url 的时候,一定要考虑到这些问题。
Manager 的流程一览
来总结一下 SDWebImageManager
在图片获取的过程中所经历的所有流程:
其中,很多合法性判断过程中,如果不满足继续下载的合法条件,则会直接调用 completedBlock 回调,并返回未下载成功的 operation 操作对象。
写在最后
至此,我们完成了对 SDWebImageManager
的分析。该系列下一篇文将对 downloader class 进行详细剖析。