SDWebImage
SDWebImage 提供一个 UIImageView 的分类以支持加载远程图片,具有缓存管理、异步下载、同一个 URL 下载次数控制和优化等特点。
工作流程
- 入口
setImageWithURL:placeholderImage:options
会先把placeholderImage
显示,然后SDWebImageManger
根据URL
开始处理图片。 - 进入
SDWebImageManager-downloadWithURL:delegate:options:userInfo:
, 交给SDImageCache
从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:
. - 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,
SDImageCacheDelegate
回调imageCache:didFindImage:forKey:userInfo:
到SDWebImageManager
。 SDWebImageManagerDelegate
回调webImageManager:didFinishWithImage:
到UIImageView+WebCache
等前端展示图片。- 如果内存缓存中没有,生成
NSInvocationOperation
添加到队列开始从硬盘查找图片是否已经缓存。 - 根据
URLKey
在硬盘缓存目录下尝试读取图片文件。这一步是在NSOperation
进行的操作,所以会主线程进行结果回调notifyDelegate:
。 - 如果上一个操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲缓存过小,会先清空内存缓存)。
SDImageCacheDelegate
回调imageCache:didFindImage:forKey:userInfo:
。进行回调展示图片。 - 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调
imageCache:didNotFindImageForKey:userInfo:
。 - 共享或重新生成一个下载器
SDWebImageDownloader
开始下载图片。 - 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
connection:didReceiveData:
中利用ImageIO
做了按图片下载进度加载效果。connectionDidFinishLoading:
数据下载完成后交给SDWebImageDecoder
做图片解码处理。- 图片解码处理在一个
NSOperationQueue
完成,不会拖慢主线程 UI。如果需要对下载的图片做二次处理,最好也在这里完成,效率会好很多。 - 在主线程
notifyDelegateOnMainThreadWithInfo:
宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:
回调给SDWebImageDownloader
。imageDownloader:didFinishWithImage:
回调给SDWebImageManager
告知图片下载完成。 - 通知所有的
downloadDelegates
下载完成,回调给需要的地方展示图片。将图片保存到SDImageCache
中,内存缓存和硬盘缓存同时保存 。写文件到磁盘也在单独的NSInvocationOperation
完成,避免拖慢主线程。 SDImageCache
在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。SDWebImagePrefetcher
可以预先下载图片,方便后续使用。
源码分析
图片下载
SDWebImageDownloader
- 单例,图片下载器,负责图片异步下载
- 图片的下载操作放在一个 NSOperationQueue 并发操作队列中,队列默认最大并发数是 6
- 每个图片对应一些回调(下载进度,完成回调等),回调信息会保存在 downloader 的 URLCallbacks 中(一个字典,key 是 url 地址,value 是图片下载回调数组),URLCallbacks 可能被多个线程访问,所以 downloader 把下载任务放在一个 barrierQueue 中,并设置屏障保证同一时间只有一个线程访问 URLCallbacks。在创建回调 URLCallbacks 的 block 中创建一个 NSOperation 并添加到 NSOperationQueue 中。
每个图片下载都是一个 operation,创建后添加到一个操作队列中,SDWebImage 定义了一个协议 SDWebImageOperation 作为图片下载操作的基础协议,声明了一个 cancel 方法,用于取消操作。
@protocol SDWebImageOperation <NSObject> - (void)cancel; @end
对于图片的下载,SDWebImageDownloaderOperation 完全依赖于 NSURLConnection 类,实现了 NSURLConnectionDataDelegate 协议的方法来跟踪图片下载进度。
SDWebImageDownloaderOperation
- 继承自 NSOperation 类,没有简单实现 main 方法,而是采用更加灵活的 start 方法,以便自己管理下载的状态。
- start 方法中创建了下载使用的 NSURLConnection 对象,开启了图片的下载,并抛出一个下载开始的通知。
- 下载的核心是利用 NSURLSession 加载数据,每个图片下载都有一个 operation 操作来完成,并将这些操作放到一个操作队列中,这样可以实现图片的并发下载。
SDWebImageDecoder
(异步对图片进行解码)
缓存
下载完图片后缓存到本地,再获取同一张图片时,直接从本地获取,减少网络流量,提升用户体验,能快速从本地获取呈现给用户。
SDWebImage 提供了对图片进行缓存,主要由 SDImageCache 完成。该类负责处理内存缓存以及一个可选的磁盘缓存,其中磁盘缓存的写操作是异步的,不会对 UI 造成影响。
- 内存缓存及磁盘缓存
- 内存缓存的处理由 NSCache 对象实现,NSCache 类似与 NSDictionary,存储 key-value 对,通常使用缓存类临时存储短时间使用但创建昂贵的对象,重用这些对象可以优化性能,同时这些对象对于内存来说不是紧要的,如果内存紧张就会自动释放。
- 磁盘缓存的处理使用 NSFileManager 对象实现,图片存储的位置位于 cache 文件夹,另外 SDImageCache 还定义了一个串行队列来处理存储图片。
- SDImageCache 提供了大量方法来缓存、获取、移除及清空图片。对于图片的索引,我们通过一个 key 来索引,在内存中,将其作为 NSCache 的 key 值,在磁盘中,用这个 key 值作为图片的文件名,使用 url 作为 key。
- 存储图片 现在内存中放置一份缓存,如果需要缓存到磁盘,将磁盘缓存操作作为一个 task 放到 串行队列中处理,会先检查图片格式,将其转换为相应的图片数据,最后把数据写入磁盘中(文件名是对 key 值做 MD5 后的串)。
查询图片
内存和磁盘查询图片 API:- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key; - (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
查看本地是否存在 key 指定的图片,API:
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompleteBlock)doneBlock;
移除图片
移除图片 API:- (void)removeImageForKey:(NSString *)key; - (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion; - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk; - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
清理图片(磁盘)
清空磁盘图片可以选择完全情况和部分清空,部分清空就是把缓存文件夹删除- (void)clearDisk; - (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
部分清理 会根据设置的一些参数移除部分文件,主要有两个指标:文件的缓存有效期(maxCacheAge: 默认是一周)和最大缓存空间大小(maxCacheSize: )
小结
SDImageCache 除了提供以上 API,还提供了获取缓存大小,缓存中图片数据等 API。
SDWebImageManger
实际使用中并不直接使用SDWebImageDownloader和SDImageCache类对图片进行下载和存储,而是使用SDWebImageManager来管理。包括平常使用UIImageView+WebCache等控件的分类,都是使用SDWebImageManager来处理,该对象内部定义了一个图片下载器(SDWebImageDownloader)和图片缓存(SDImageCache)。
视图扩展
技术点
1、dispatch_barrier_sync函数,用于对操作设置顺序,确保在执行完任务后再确保后续操作。常用于确保线程安全性操作。
2、NSMutableURLRequest:用于创建一个网络请求对象,可以根据需要来配置请求报头等信息
3、NSOperation及NSOperationQueue:操作队列是OC中一种告诫的并发处理方法,基于GCD实现,相对于GCD来说,操作队列的优点是可以取消在任务处理队列中的任务,另外在管理操作间的依赖关系方面容易一些,对SDWebImage中我们看到如何使用依赖将下载顺序设置成后进先出的顺序
4、NSURLSession:用于网络请求及相应处理
5、NSCache类:一个类似于集合的容器,存储key-value对,这一点类似于nsdictionary类,我们通常用使用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃
6、清理缓存图片的策略:特别是最大缓存空间大小的设置。如果所有缓存文件的总大小超过这一大小,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间。
7、图片解压操作:这一操作可以查看SDWebImageDecoder.m中+decodedImageWithImage方法的实现。