OpenSource-SDWebImage
SDWebImage在iOS开发中是比较常见的一个开源库,实现的网络及本地图片的异步加载及缓存功能。主流的图片格式基本都支持,甚至包括GIF和WebP。主要特性有:常用UI控件类别支持、异步加载、异步的内存与磁盘缓存周期自动化处理、后台解码、同一URL地址图片不会重复加载、无效的URL地址图片不会反复加载、非阻塞主线程、良好的性能等。关于这个开源库的源代码解析网上也有很多,这篇文章所呈现的,是基于我自己的学习和理解
类图
下图是官方文档中提供的SDWebImage库的类图,详细展现了该库的功能模块的划分和构成,可以整体上对这个库的层次结构有个了解
SDWebImageClassDiagram.png时序图
下图是官方文档中提供的一张典型的图片加载过程,从调用category的sd_setImageWithURL接口到最后调用ImageView的setImage方法的完成图片加载的整个过程。那就以这张图为思路,逐步拆解分析和学习吧
SDWebImageSequenceDiagram.pngCategory
SDWebImage主要通过category+block的方式提供接口调用,实现对原有工程代码无侵入,同时图片的加载进度及完成事件都以block方式回调,使用便捷,例如最常见的sd_setImageWithURL方法簇,在UIKit框架中的基础视图和常用控件中都有category的实现,例如UIImageView、UIView、UIButton、UIImage等。下面是几个比较常用的类别方法,最终都会调用到UIView (WebCache)的sd_internalSetImageWithURL核心方法中去
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
............
Image URL
调用方式知道了,是category,那接下来第一个问题,就是我们请求加载的图片地址,是以什么方式保存管理的呢?答案是Runtime中的objc_setAssociatedObject及objc_getAssociatedObject方法,在运行时动态的将url值绑定到具体的对象(例如ImageView)中,以imageURLKey全局变量作为绑定值的key,代码如下:
static char imageURLKey;
...
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
...
objc_getAssociatedObject(self, &imageURLKey);
类似的动态值绑定还有TAG_ACTIVITY_INDICATOR、TAG_ACTIVITY_STYLE、TAG_ACTIVITY_SHOW,用以控制图片加载时是否使用Indicator,Indicator的显示风格等
Load Error
在SDWebImage中比较常见的一个问题就是载入失败了的图片如何处理,在SDWebImageManager核心类中,发生过载入错误的图片URL会存入failedURLs中,在每次执行下载图片请求前会检测其URL是否曾经失败过,如果失败过并且没有设置失败重试标志(SDWebImageRetryFailed),SDWebImageManager会直接调用completedBlock返回错误,不会继续后面的网络下载操作
Cache
在SDWebImageManager执行正式下载之前,会先通过Cache机制查找本地是否已经存在所请求的图片(以URL String做为key来匹配),保证最优性能
缓存机制在SDWebImage中有着很好的支持,由SDImageCache类负责管理,例如缓存位置(内存、磁盘),缓存空间(路径、大小限制),缓存周期等
-
从Cache查询图片时,先从内存缓冲空间(NSCache)查找,如果命中,直接返回图片
-
内存缓冲空间不存在,再从磁盘缓冲空间(diskCachePath)查找,如果命中,先判断config是否指定缓存在内存的标志位(shouldCacheImagesInMemory),如果指定,计算其占用大小,然后放到内存缓冲空间(NSCache)中,最后返回图片
-
为了良好的性能及Cancel机制,在涉及到磁盘I/O操作的地方,SDImageCache使用了独立的GCD Dispatch_Queue来实现异步加载;以SDWebImageOperation为操作单位,标记URL与Cache Operation的对应关系,实现缓存读取的Cancel机制
-
Cache中提供了一些自动化的有效周期管理,在应用收到内存警告、进行后台或者退出时,根据设置的规则进行缓存清理工作,例如maxCacheAge,缓冲图片的最长保留时间;maxCacheSize,最大的缓存占用空间
Download
在缓存SDImageCache查询结束后,下载操作之前,仍会有些预判断条件,例如缓存是否命中、是否config中设置了强制刷新标志(SDWebImageRefreshCached)、Delegate询问是否可以加载图片(imageManager:shouldDownloadImageForURL:)等,SDWebImageManager确定是否需要执行真正下载流程
SDWebImageDownloader类是SDWebImage中真正负责调度执行下载任务的核心类,主要包括以下几方面:
-
NSURLSession,网络会话,共享给SDWebImageDownloaderOperation类使用,负责具体下载任务单元中的网络调用,必要时,下载任务单元中也会自己创建独立的Session使用
-
barrierQueue,同步队列,以用同步所有网络请求返回结果及下载任务,在Downloader添加新下载任务及取消下载任务都以这个串行队列同步执行,保证线程安全。在SDWebImageDownloaderOperation中也使用了barrierQueue,来同步callbackBlock回调的新增、删除和查询,保证同一时刻相同URL请求不会被重复下载。
-
URLOperations,字典,SDWebImageDownloader中,管理URL与下载任务(SDWebImageDownloaderOperation)的对应关系
-
callbackBlocks,字典型数组,SDWebImageDownloaderOperation中,管理progress与completed回调block
-
NSOperationQueue,下载任务执行队列,用以执行SDWebImageDownloaderOperation封装的具体下载任务
-
SDWebImageDownloaderOperation:下载任务执行单元,继承自NSOperation,实现NSURLConnectionDataDelegate与NSURLSessionTaskDelegate协议,定义了枚举类型SDWebImageDownloaderOptions用以控制下载的优先级、缓存、后台任务执行、渐进式下载、cookie处理以及证书认证几个方面,在创建下载操作的时候可以使用组合的选项以完成一些特殊的需求。
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { SDWebImageDownloaderLowPriority = 1 << 0, SDWebImageDownloaderProgressiveDownload = 1 << 1, /** * By default, request prevent the use of NSURLCache. With this flag, NSURLCache * is used with default policies. */ SDWebImageDownloaderUseNSURLCache = 1 << 2, /** * Call completion block with nil image/imageData if the image was read from NSURLCache * (to be combined with `SDWebImageDownloaderUseNSURLCache`). */ SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, /** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ SDWebImageDownloaderContinueInBackground = 1 << 4, /** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ SDWebImageDownloaderHandleCookies = 1 << 5, /** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, /** * Put the image in the high priority queue. */ SDWebImageDownloaderHighPriority = 1 << 7, /** * Scale down the image */ SDWebImageDownloaderScaleDownLargeImages = 1 << 8, };
总结
下面这张图是我按照自己对SDWebImage的功能模块、调用流程、所属线程的理解,大致画的一张简略的示意图:
SDWebImage.pngTips:block循环引用的解决办法
所谓循环引用,即在实例对象的block块中使用self.语法,导致self实例持有block,而block也持有了self实例,通常的做法是使用__weak达到block始终不持有self实例,解除了循环引用关系,从双向变成了单向,但仍然存在风险,例如block块在执行期间,self实例已经销毁
在SDWebImage的代码中,通过在block块中再强持有前面声明的weak self,达到只在block块运行期间持有可用的self实例,同时仍然维持了单向引用关系,基本降低了crash的风险,非常巧妙的用法,如下:
......
// 声明弱引用实例指针变量,使block块不再持有实例
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url
options:options
progress:progressBlock
completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// 块中声明强引用实例变量,持有弱引用实例指针变量,达到只在block块内执行期间持有可用的self实例
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
......