SDWebImage源码解析(二)

SDWebImage源码解析(一)中,我从宏观上介绍了SDWebImage项目,并详细介绍了UIImageView+WebCacheSDWebImageManager两个类。现在我们继续研究SDWebImageDownloaderSDImageCache

SDWebImageDownloader

Asynchronous downloader dedicated and optimized for image loading.

SDWebImageDownloader是专用的且优化的图片异步加载器。先了解一下下载选项:

再看看下载顺序:

SDWebImageDownloader也定义了三个block:

类方法分别是:

实际上,SDWebImageDownloader管理一个下载队列downloadQueue,默认最大的并行操作个数是6。队列中每一个SDWebImageDownloaderOperation实例才是真正的下载请求执行者。
我们重点研究核心下载方法

该方法就是调用了另外一个关键方法:

该方法为下载的操作添加回调的块, 在下载进行时, 或者在下载结束时执行一些操作。图片下载的progressBlockcompletedBlock回调由一个字典URLCallbacks管理。字典的key是图片的url,value 是一个数组,数组只包含一个元素,这个元素的类型是NSMutableDictionary类型,这个字典的key为NSString类型代表着回调类型,value为block,是对应的回调。由于允许多个图片同时下载,因此可能会有多个线程同时操作URLCallbacks属性。为了保证线程安全,将下载操作作为一个个任务放到barrierQueue队列中,并设置栅栏来确保同一时间只有一个线程操作URLCallbacks属性
两个回调对应的key分别是

如果URLCallbacks没有url这个key,说明是第一次请求这个url,需要调用createCallback创建下载任务,即使用

初始化SDWebImageDownloaderOperation实例。
下载任务使用NSMutableURLRequest,默认超时时间是15秒。
在progress block中我们取出存储在URLCallbacks中的progressBlock

对已经接收到的大小和期待的大小调用callback;
在completed block中我们取出存储在URLCallbacks中的completedBlock

对image和data调用callback;
在cancelled block中,我们移除存储在URLCallbacks的数组。
初始化完成后,再设置operation的参数:

最后将这个SDWebImageDownloaderOperation实例添加到downloadQueue队列中去。如果下载执行顺序是LIFO,还要加上任务的依赖

SDWebImageDownloaderOperation

现在我们来研究一下上面提到的SDWebImageDownloaderOperation类。
SDWebImageDownloaderOperation是NSOperation的子类,遵循SDWebImageOperationNSURLSessionTaskDelegateNSURLSessionDataDelegate协议,并重写了start方法。在start方法中真正处理HTTP请求和URL链接。
首先监测下载状态:

如果是iOS4.0以上的版本,还需要考虑是否在后台执行:

Version3.8中,下载已经由原先的NSURLConnection切换到了NSURLSession了:

创建好任务后开始执行请求。 如果任务创建成功,可能需要调用progressBlock回调并发送下载开始的通知;如果创建失败,直接执行完成回调,并传递一个connection没有初始化的错误:

任务开始后,我们需要关注NSURLSessionDataDelegate的几个代理方法。
首先是

此代理方法告诉delegate已经接受到服务器的初始应答, 准备接下来的数据任务的操作。这里主要可讲的是对返回码为304的处理。在HTTP的返回码中,304表示服务端资源未改变,可直接使用客户端未过期的资源,我们需要取消operation并返回缓存中的image。
其次是

此代理方法告诉delegate已经接收到部分数据,拼接数据。

另外还有NSURLSessionTaskDelegate的两个代理方法:

这里就不再一一赘述了。

SDImageCache

现在我们研究缓存部分,即SDImageCache类。

SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed asynchronous so it doesn’t add unnecessary latency to the UI.

SDImageCache维持了一个内存缓存memCache和一个可选的磁盘缓存fileManager,磁盘缓存的写操作时异步的。
内存缓存是用NSCache实现的,以Key-Value的形式存储图片,当内存不够的时候会清除所有缓存图片。磁盘缓存则是缓存到沙盒中,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。
先来看看几个重要的属性:

再看看几个重要的方法:

disk cache的文件名是key做MD5后的字符串:

我们重点研究怎么存储到缓存中的,storeImage:forKey:storeImage:forKey:toDisk:最终都是调用storeImage:recalculateFromImage:imageData:forKey:toDisk:方法的。
如果需要存储到memory cache中,首先存入memory cache。

如果需要存储到disk cache,在子线程中串行存储到disk cache中:

最终真正存储到磁盘中的方法是:

再来看看图片查询的几个方法。在SDWebImageManager中的downloadImageWithURL:options:progress:completed方法中使用到了imageCache的queryDiskCacheForKey:done方法。这是SDImageCache里面查询图片的入口。
首先,从memory cache中查询,如果找到图片就直接使用并返回:

否则,去disk cache中查询,同样是在子线程的同步队列中执行。如果找到,还需要监测是否需要存储到memory cache中:

其中,在memory cache 中查询很简单,直接使用字典方法objectForKey:

在disk cache中查询,需要根据key构造各种可能的路径。最后如果找到,需要缩放或者解压缩:

最后来看看图片的清理方式。移除指定key对应的图片有一系列方法,最终调用的方法是:

而清空cache有两种方式,即完全清空与部分清空。对于memory cache是完全清空的:

对于disk cache,两种方式都有可能。完全清空的方式是直接把文件夹移除掉:

部分清空是根据参数配置移除文件,使文件的总大小小于最大使用空间。清理策略有两个:

  1. 文件的缓存有效期:默认是一周。如果文件的缓存时间超过这个时间值,则将其移除。
  2. 最大缓存空间大小:如果所有缓存文件的总大小超过最大缓存空间,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间。

至此,我们已经把SDWebImage最主要的几个模块分析清楚了,我们可以绘制一个流程图来对各个模块的工作流做个总结:

1136939-289376c09e4d45fd

SDWebImage_流程图.png

延伸

最后,我们延伸一点知识,讲讲前面提到的dispatch_main_sync_safe宏、dispatch_main_async_safe宏以及SDWebImageDecoder的作用。

这两个宏比较简单,直接看代码:

即保证当前代码在主线程中执行,上面是同步调用,下面是异步调用。
判断主线程的目的是避免出现死锁问题:
如果在主线程中执行dispatch_sync(dispatch_get_main_queue(), block) 同步操作时,会出现死锁问题,因为主线程正在执行当前代码,根本无法将block添加到主队列中

SDWebImageDecoder用来解压缩图片,关于为什么从磁盘读取image后要做一次解压缩,参考了v2panda的解释,仅供大家参考。

因为通过 imageNamed 创建 UIImage 时,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。具体的说就是一个UIImage加载了jpeg或者png,当UIImageView将要显示这个UIImage的时候会先把png和jpeg解码成未压缩格式,所以SDWebImage有一个decodeImage方法,就是把这一步放在了异步线程做,防止tableViewCell中的imageView加载图片的时候在主线程解码图片,导致滑动卡顿。这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了,这种做法是典型的空间换时间的做法,如下从硬盘中去图片时,分别对图片进行了缩放和解压缩操作。

1 2 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部