前言
SDWebImage强大的网络图片加载库,以前只是能够灵活使用,对底层的实现原理,也是知其然而不知其所以然,但是成为一名优秀的开发者来说,会用只是最简单的一步,更重要的是要研究其底层的技术实现和设计思路原理。这个月工作也不忙,所以阅读下类库源码。记录下自己的思考与总结。
SDWebImage 简介
- 提供UIImageView的一个分类,用来加载网络图片并且对网络图片的缓存进行管理
- 采用异步方式来下载网络图片
- 采用异步方式,使用memory+disk来缓存网络图片,自动管理缓存
- 支持GIF动画
- 支持WebP格式
- 后台图片解压缩处理
- 确保同一个URL的图片不被重复下载
- 失效的URL不会被无限重试
- 下载及缓存时,主线程不被阻塞
下载
在SDWebImage中,图片的下载是由SDWebImageDownloader类来完成的,它是一个异步的下载器。并且对图片的加载做了优化处理。
下载选项
在下载的过程中,程序会根据设置的不同的下载选项,而执行不同操作。下载选项由枚举SDWebImageDownloaderOptions定义,定义如下:
|
|
选项涉及了下载优先级,缓存策略,后台运行,cookie处理及认证
下载选项
SDWebImage定义了两种下载顺序,定义如下:
|
|
下载管理器
SDWebImageDownloader下载管理器是一个单例类,主要管理图片的下载操作。图片的下载是放在NSOperationQueue操作队列中来完成的,队列的默认最大并发数为6.设置超时时间为15s.
所有下载操作的网络响应序列化处理是放在一个自定义的并行调度队列中来处理的,其声明及定义如下:
|
|
下载回调
|
|
图片下载的回调信息存储在URLCallbacks属性里面。该属性是一个字典。key是图片的URL地址,value则是一个数组,包含每个图片的多组回调信息。value(数组里面)只包含一个元素,这个元素的类型是NSMutableDictionary类型,这个字典的key为NSString类型代表着回调类型,value为block,是对应的回调。目的都是为了给url绑定回调。由于允许多个图片同时下载,因此可能会有多个线程同时操作URLCallbacks属性。为了保证线程安全。将下载操作作为一个个任务放到barrierQueue队列中。并设置栅栏来确保同一时间只有一个线程操作URLCallbacks属性
|
|
在barrierQueue队列中创建下载任务。至此下载的任务都创建好了,下面到下载的操作了
|
|
下载操作
每个图片的下载都是一个Operation操作。SDWebImage自定义了一个SDWebImageDownloaderOperation类,继承自NSOperation。并遵守SDWebImageOperation协议。对于图片的下载。使用的是NSURLConnection(未使用7.0以后的NSURLSession)。在SDWebImageDownloaderOperation中重写了start方法,方便自己管理下载的状态。此方法是执行下载任务的核心代码。
|
|
主要看一下connection: didReceiveData拼接数据的协议
|
|
下载的核心是利用NSURLConnection对象来加载数据。每个图片的下载都由一个Operation操作来完成,并将这些操作放到一个操作队列中。这样可以实现图片的并发下载。之前在做好唱项目时,伴奏的下载也是采用这用设计思路。
缓存
SDWebImage提供了对图片缓存的支持,而该功能是由SDImageCache类来完成的。该类负责处理内存缓存及磁盘缓存。其中磁盘缓存的写操作是异步的。
内存缓存
NSCache是一个类似于集合的容器,即缓存。它存储key-value,这一点非常类似 NSDictionary。 一般用 NSCache来缓存临时存储短时间但是使用创建成本高的对象,重用这些对象可以优化性能,因为他们的值不需要被重新计算。另外一方面,这些对象对于程序来说是不要紧的,在内存紧张的时候会被丢弃,如果对象被丢弃了,则下次使用的时候需要重新计算
磁盘缓存
磁盘缓存则是使用NSFileManager对象来实现的。图片存储的位置是位于Cache文件夹。此外SDImageCache还定义了一个串行队列,来异步存储图片
图片存储
在iOS中,会先检测图片是PNG还是JPEG,并将其转换为相应的图片数据,最后将数据写入到磁盘中。判断是否是png格式的文件,除了看是不是.png后缀格式命名外,还能分析文件开头的部分数据,这部分数据就是文件签名,每个标准的PNG文件开头都有固定格式的数字签名。详细参考文件名的命名规则则是按照缓存的key做md5处理。
|
|
当查询图片时,该操作会在内存中放置一份缓存,如果确定需要缓存到磁盘,则将磁盘缓存操作作为一个task放到串行队列中处理。
|
|
图片查询
使用key作为参数,查询内存和磁盘中是否有对应的图片
|
|
图片移除
使用如下操作可以移除内存或者磁盘上的图片。
|
|
图片清理
两种清理方式:完全清空和部分清空
完全清空是直接把文件夹移除掉
|
|
部分清空是根据参数配置、移除文件。使文件的总大小小于最大使用空间。清理策略有两个文件缓存的有限期时间及最大缓存空间大小。
文件的缓存有效期:默认是一周。如果文件的缓存时间超过这个时间值,则将其移除
最大缓存空间大小:如果所有缓存文件的总大小超过最大缓存空间,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间
|
|
对于缓存操作总结:
- 缓存分为内存缓存和磁盘缓存(以NSCache和文件的形式)
- 读取先从内存查找、如果没有再从磁盘读取并放入内存
- 提供完全移除和部分移除功能,部分移除根据配置、达到删除文件后的容量小于用户设定的最大值
SDWebImageManager
|
|
在开发应用中,SDWebImageManager 管理图片的下载和缓存,所以提供了SDImageCache和SDWebImageDownloader两个类的属性,我们经常用到的诸如UIImageView+WebCache等控件的分类都是基于SDWebImageManager对象的。SDWebImageManagerDelegate还声明了两个可选实现的方法。
|
|
在查看源码时,发现了有趣的宏定义。
|
|
保证了在主线程执行。
补充点
之前一直不明白,为什么将图片从磁盘读取出来后需要做Decode,后来参考
才明白。由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了。这是典型的空间换时间的做法。