SDWebImage源码阅读笔记(一)

在做iOS开发中加载图片是经常性工作,一种是使用UIImage加载本地图片,使用[UIImage imageNamed:@""][UIImage imageWithContentsOfFile:@""]等方法,各有侧重优劣,不是本篇重点不必赘述。另一种实时从网络加载,其中一种方法是从服务端获取图片的二进制数据,客户端将其转化为NSData *类型,再通过UIImage加载,这种方式适合小批量的图片加载,安全性好实用简便,另一种方法就是服务端先提供一个图片的URL,客户端再通过URL加载图片,这个适合大批量获取图片的场景,在iOS工程中也是广泛实用的场景,也是本篇讨论的重点。

如果不使用第三方框架,最简单的方法便是先调用[NSData dataWithContentsOfURL:url],再使用[UIImage imageWithData:data]加载图片,通常这个过程可以使用GCD等异步加载方式防止阻塞UI主线程。但通常情况下为了使用方便和提高性能,通常要使用一些封装的框架,这其中就有大名鼎鼎的SDWebImage,这也是本篇文章要介绍的。

首先,还是来大概浏览一下其结构:

SDWebImage源码阅读笔记(一)_第1张图片

大概可以看出大概分为loader下载器,cache管理,图片加载等部分,下面沿袭之前风格,还是通过一个在工程中的简单调用来分析其工作原理。

下面就从一个UIImageView加载一个图片的URL开始:

以上就是一个一个在UITableViewcell中一个普通调用,可以看出这个框架主要使用了类别来实现。

下面进入API查看:

发现其APIUIImageView+WebCache的类别中:

SDWebImage源码阅读笔记(一)_第2张图片
SDWebImage源码阅读笔记(一)_第3张图片

最终调用方法中参数:url为图片网络地址,placeholder为占位图,options为下载处理选择项默认为SDWebImageRetryFailed(也就是说加载失败这个URL就会被拉入黑名单不会重复加载),operationKey为网络操作标识符,setImageBlockprogressBlockcompletedBlock这三个block会在不同时间调用,这些在后续分析中都会着重解说。

再继续点进去就到了UIView+WebCache这个类别:

SDWebImage源码阅读笔记(一)_第4张图片
SDWebImage源码阅读笔记(一)_第5张图片

这一步多了一个context参数,这个会死一个字典类型,主要管理一些dispatch_group_t,后面会分析。

再继续就开始脱去层层外衣见识真相了:

SDWebImage源码阅读笔记(一)_第6张图片

下面开始逐行代码分析:

NSString *validOperationKey = operationKey ?:NSStringFromClass([selfclass]);如果上边的参数operationKey为空的话,就创建这个key值,但是用来做什么呢?向下看:

[self sd_cancelImageLoadOperationWithKey:validOperationKey];从字面意思上看是要通过这个key值来撤销一些操作。可以进去详细看:

SDWebImage源码阅读笔记(一)_第7张图片
SDWebImage源码阅读笔记(一)_第8张图片

由上可以看出,UIView+WebCacheOperation这个类别维护了一个NSMapTable *类型的属性,NSMapTable类似于字典吧,这个字典就是以operationKeykey值,遵守协议的对象为value值,这行代码就是通过key找到这个operation,将其撤销,并从字典中删除,书中代言operation就是用来做图片依次IO的操作,后边还会详细介绍。

objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);这一步是将url保存为这个UIView+WebCache的一个关联类,类似于属性。

dispatch_group_t group = context[SDWebImageInternalSetImageGroupKey];这个是用来做一个任务队列管理。

SDWebImage源码阅读笔记(一)_第9张图片

这一步是根据options值决定是否立即显示占位图,

SDWebImage源码阅读笔记(一)_第10张图片
SDWebImage源码阅读笔记(一)_第11张图片

这一步就知道setImageBlock这个参数是用来做什么的,

SDWebImage源码阅读笔记(一)_第12张图片

这里可以看出这个block其实目的是用来将image给对应的控件一般是UIImageView赋值的,如果为空就构造一个finalSetImageBlock,最终在这一步

SDWebImage源码阅读笔记(一)_第13张图片

调用这个block给相应图片显示控件赋值。

继续向下走:

SDWebImage源码阅读笔记(一)_第14张图片

如果url存在,会通过urlIO图片,如果不存在也会有个错误处理。

现在开始具体分析url存在下的图片加载和缓存机制,这个是重点:

self.sd_imageProgress.totalUnitCount = 0;

self.sd_imageProgress.completedUnitCount = 0;

这两行从字面就可以看出是重置加载进度。

SDWebImage源码阅读笔记(一)_第15张图片

到这一步,SDWebImageManager浮出水面,SDWebImageManager也是一个很重要的部分,相当于这个框架的一个管理类。

SDWebImage源码阅读笔记(一)_第16张图片

进去可以看到,这个类的全局单例对象负责管理缓存、加载、失败处理和运行operation(上文有提到过)。

SDWebImage源码阅读笔记(一)_第17张图片

这一步可看出是对progressBlock的处理,从字面可看出是对加载进度的一个处理,如果工程中需要对加载进度进行处理,可实现这个block

这一步就是具体的operation操作。也是即将需要大量笔墨分析的部分。

展开这部分代码:

SDWebImage源码阅读笔记(一)_第18张图片

可看出在completedblock中是operation结束后的处理,也就是说在调用这个block时,image已经完成加载和缓存了,是最后一步,既然是最后一步,暂且不谈押后处理。

下面先重点分析一下operation的构造方法:

SDWebImage源码阅读笔记(一)_第19张图片
SDWebImage源码阅读笔记(一)_第20张图片

可以看到operation是遵守协议的SDWebImageCombinedOperation类的对象,这个方法的调用者便是SDWebImageManager类的单例对象。

SDWebImage源码阅读笔记(一)_第21张图片

这部分是对url的一个处理以及和operation的创建。

SDWebImage源码阅读笔记(一)_第22张图片

manager维护了一个failedURLs的数组,从字面可看出failedURLs是一个加载失败的url的数组,这里会判断,如果url为空,或者符合花括号中的条件,就会走这个方法[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];展开这个方法:

SDWebImage源码阅读笔记(一)_第23张图片

可以看出这个方法就是处理上文提到过的构造方法中最后一个block参数的,在这里调,说明整个operation也就结束了,当然是以加载失败告终。

SDWebImage源码阅读笔记(一)_第24张图片

这一步将operation加入到manager维护的runningOperations数组中,LOCK锁是用信号量dispatch_semaphore_t实现的。然后通过url得到key值,在

SDWebImage源码阅读笔记(一)_第25张图片

中,可以看出我们可以对url加自定义的过滤器来获取key值。

SDWebImage源码阅读笔记(一)_第26张图片

这一步是缓存一个SDImageCacheOptions的缓存加载查询策略类型的cacheOptions

紧接着是构建一个缓存的operation,

SDWebImage源码阅读笔记(一)_第27张图片
SDWebImage源码阅读笔记(一)_第28张图片

可以看出SDWebImageCombinedOperation实际上是一个对operation的管理类,cacheOperation才是具体图片IOoperation

从构造方法中,可看出done后边的block应该是最后一步执行的block,暂且不论,先进去看cacheOperation的具体构造方法。

SDWebImage源码阅读笔记(一)_第29张图片

这个构造方法是由manager维护的SDImageCache *类型的imageCache来完成的。imageCache是框架的缓存管理类。

SDWebImage源码阅读笔记(一)_第30张图片

先对key做判断处理,若为空直接执行doneBlock

SDWebImage源码阅读笔记(一)_第31张图片

首先从内存缓存中获取,如果内存中存在且满足花括号中的条件直接返回。

SDWebImage源码阅读笔记(一)_第32张图片

接着,从硬盘缓存去获取。由于磁盘IO比较缓慢,所以提供了一个异步任务队列,可以选择异步磁盘获取,可见作者思虑之周详。

展开queryDiskBlock:

SDWebImage源码阅读笔记(一)_第33张图片

先从磁盘中获取到diskData数据,若内存中已经获取到image便赋值diskImage = image;否则再将diskData解码后的数据赋值给diskImage,然后调用[self.memCache setObject:diskImage forKey:key cost:cost];将其存入内存缓存,最后再执行doneBlock

由上边方法可看出,从磁盘中获取数据时解码是一个很大的耗时耗性能操作,恰好SDWebImage能很巧妙的解决这个问题,其解决方法就是先将其绘制成bitmap数据到画布上,这些在进行网络获取时还会用到,之后再详细讲解,这也是SDWebImage很经典的一部分。

现在就回来再看doneBlock中部分:

SDWebImage源码阅读笔记(一)_第34张图片

由于block会强持有对象,所以这里要对operationweak处理。

如果operation被撤销,则会被从runningOperations数组中安全移除。

SDWebImage源码阅读笔记(一)_第35张图片

根据加载策略,是否从缓存中取得图片等条件确定是否从网络下载。

SDWebImage源码阅读笔记(一)_第36张图片

如果需要不需要从网络下载,也会回调执行最后的completedblock,并从数组中安全移除operation

如果需要从网络加载,走下面的选择支:

SDWebImage源码阅读笔记(一)_第37张图片
SDWebImage源码阅读笔记(一)_第38张图片

如果从缓存中加载到图片,切加载策略为从网络刷新,则先返回执行completedBlock,但事没完,还要继续从网络加载刷新,这个适合可能在后台同一个url换另一张图片的情形。

SDWebImage源码阅读笔记(一)_第39张图片

设置一个网络下载策略。

SDWebImage源码阅读笔记(一)_第40张图片

接着构造了一个downloadToken作为属性赋给了operation。可能我们会好奇,downloadToken是啥,点进去看:

SDWebImage源码阅读笔记(一)_第41张图片

可以看得出,这个token的作用是作为一个下载的独立标示,这样一来图片加载处理的operation可以通过持有的这个token来进行撤销下载等操作。

从上边这整块代码来看,可以发现是又manager维护的imageDownloader下载器负责下载图片的,并返回这个token

在这个方法中传入了urldownloaderOptions下载策略、progressBlockcompletedBlock等参数,progressBlock就是前文API过程调用的blockcompletedBlock从字面上看应该也是结束后调用的block,暂且不论,首先来分析这个下载器的工作:

SDWebImage源码阅读笔记(一)_第42张图片

可以看出SDWebImageDownloader其实是一个下载管理类,imageDownloader就是这个管理类的一个单例对象,负责在运行期间管理图片下载,维护者下载operationURLOperations字典,downloadQueue下载任务队列等。

逐步展开来看:

SDWebImage源码阅读笔记(一)_第43张图片

如果url为空,直接返回空,并接受操作。

SDWebImage源码阅读笔记(一)_第44张图片

先根据以urlkey值从self.URLOperations中查找具体的下载operation。如果不存在或已结束或已撤销就创建一个新的并加入self.URLOperations字典和self.downloadQueue队列。如果存在,但并不在执行中,可根据operation下载策略来调整下载operation的优先级。整个过程都是在线程安全中进行的。

紧接着构造SDWebImageDownloadToken *token返回赋给图片加载处理的operationdownloadToken

下面继续深入分析具体的下载operation:

SDWebImage源码阅读笔记(一)_第45张图片

可以看到下载operation其实是NSOperation的一个实例,遵守协议。

展开分析:

SDWebImage源码阅读笔记(一)_第46张图片

设置超时时间,网络请求安全策略,构造网络请求request

SDWebImage源码阅读笔记(一)_第47张图片

根据request构造相应的operation,并根据下载策略options做优先级处理等。

进入operation的构造方法:

SDWebImage源码阅读笔记(一)_第48张图片

可以看出operation的类其实是框架自定义的继承于NSOperation的类。其任务主体主要是通过重写的start函数实现的:

SDWebImage源码阅读笔记(一)_第49张图片

首先也是用@synchronized 锁来锁住相关代码,这部分代码就是初始化sessiondataTask部分。

展开这个锁:

SDWebImage源码阅读笔记(一)_第50张图片

如果任务已经被撤销,重置。

SDWebImage源码阅读笔记(一)_第51张图片

设置应用退入后台的处理。

SDWebImage源码阅读笔记(一)_第52张图片

初始化session,不过这里的session一般使用的是初始化方法传进来的session,一般是由imageDownloader维护的一个框架全局的session

SDWebImage源码阅读笔记(一)_第53张图片

根据下载策略option设置网络请求缓存策略。

通过sessionrequest构造请求任务dataTask,并将任务operationexecuting状态设置为执行。

SDWebImage源码阅读笔记(一)_第54张图片

根据下载策略option设置dataTask的优先级,并运行dataTask

回头发现imageDownloader的初始化函数中

sessiondelegate给了imageDownloader,但同样SDWebImageDownloader也继承了协议,并实现了具体协议方法。

而在协议方法中:

SDWebImage源码阅读笔记(一)_第55张图片

又将回调方法在下载dataOperation中执行,所以再来到dataOperation中实现的代理方法:

SDWebImage源码阅读笔记(一)_第56张图片

首先在线程安全下降dataTask置空,紧接着就开始处理返回的图片的二进制数据:

SDWebImage源码阅读笔记(一)_第57张图片

除去错误处理等,核心代码是这块:

SDWebImage源码阅读笔记(一)_第58张图片

这块主要就是在self.coderQueue异步队列中完成图片解码,下面逐步分析:

SDWebImage源码阅读笔记(一)_第59张图片

在这里SDWebImageCodersManager这个解码管理类就粉墨登场了,同样也是实现了一个全局的单例实例来做解码工作。

首先看一下SDWebImageCodersManager的初始化方法:

SDWebImage源码阅读笔记(一)_第60张图片

初始化中除了实现安全锁外,更重要的初始化这个数组_coders很重要,这个数组目前只有一个元素SDWebImageImageIOCoder*类型的实例。

然后进入第一个解码方法:

SDWebImage源码阅读笔记(一)_第61张图片

根据初始化方法会走到SDWebImageImageIOCoder*类型的实例方法:

SDWebImage源码阅读笔记(一)_第62张图片

第一行代码是普通的解码方法,第二行代码是框架通过NSData的类别实现的一个查看图片类型的方法不再详述。

然后获取图片的缓存key值,并矫正图片方向。

然而就此结束了吗?向下走:

SDWebImage源码阅读笔记(一)_第63张图片

这里有又了一个解码方法,这点可能就会引起大家的困惑,为什么要这样呢?原来[[UIImage alloc] initWithData:data]这个方法并没有实际解码,只有将image第一次显示的时候才会解码,并长久滞留内存,这并不是一个很好的处理方法。这一点也就是SDWebImage在那个时代很精华的一部分,通过调用

这个方法将图片绘制到CG画布上,控件直接加载画布上的image,这在当时是一个巨大的创新,下面就具体分析这个方法:

SDWebImage源码阅读笔记(一)_第64张图片

如果下载策略不包含大图片等比例缩小的话将走到这个方法:

SDWebImage源码阅读笔记(一)_第65张图片

展开分析:

做个判断,如果image为空或是动画直接返回。

紧接着就是CG的天地了:

SDWebImage源码阅读笔记(一)_第66张图片

主要思路竟是通过创建一个bitmap context,将图片绘制到画布上,才从画布上获取image

就此解码工作完成,也会产生长久滞留内存的图片信息数据。顺便提到上文在从硬盘中获取缓存时卖的关子,从硬盘IO获取到图片的二进制数据也走的是这条线。

就此image加载也就完成了,先回到下载operation的回调方法中,还在那个图片解码的队列coderQueue的代码块中:

进去这个方法:

SDWebImage源码阅读笔记(一)_第67张图片
SDWebImage源码阅读笔记(一)_第68张图片

可以看出是通过下载operation维护的callbackBlock数组找到回调的completedBlock执行回调,到这里同学们是否有迷路的,能否找到回家的路呢?

回到这个下载operation的构造方法:

SDWebImage源码阅读笔记(一)_第69张图片

可以看出就是在

SDWebImage源码阅读笔记(一)_第70张图片

这个方法中将过程progressBlock和完成completedBlock传入下载operation中的。

到此为止,整个网络下载过程也就分析完了,回到SDWebImageManager勒种图片加载operaion的构造方法中,

SDWebImage源码阅读笔记(一)_第71张图片

现在就到这completedBlock的执行部分。

正常的话就会走到这个选择支:

SDWebImage源码阅读笔记(一)_第72张图片

先做缓存,由manger持有的缓存管理imageCache来执行:

SDWebImage源码阅读笔记(一)_第73张图片

展开分析:

SDWebImage源码阅读笔记(一)_第74张图片

如果image或key不存在,直接返回。

SDWebImage源码阅读笔记(一)_第75张图片

将解码后的image放入内存缓存,下次从内存中加载就不需要解码。

SDWebImage源码阅读笔记(一)_第76张图片

异步磁盘IO,将图片压缩后二进制数据存入硬盘。

结束缓存后:

SDWebImage源码阅读笔记(一)_第77张图片

终于到了激动人心的时候了,就像一个走了很多路的孩子终于要回家了,终于回调框架API中的completionBlock了。

展开分析,除去过程处理和重绘处理,主干代码走到这里:

SDWebImage源码阅读笔记(一)_第78张图片

赋值要处理的imagedata,再走到之前提到到的

方法,给相应控件赋值,就此一个调用结束,可以长吁一口气了。

说了这么多,感觉是时候总结一下了,

从网路上借张图:

SDWebImage源码阅读笔记(一)_第79张图片

来自SDWebImage源码分析 原

按照本篇的分析,整个路径是这样的:首先调用加载图片的UIImageVIew*类型的imageView的分类UIView+WebCacheAPI,在API方法中,通过id的对象,也就是SDWebImageManager*类型的manager,通过manager构造加载图片的id operation,也就是SDWebImageCombinedOperation *类型的operation,并由分类UIView+WebCache持有的sd_operationDictionary字典来管理。然后通过id operation来构造并持有cacheOperation来获取缓存,同时如果需要从网络下载,便构造持有网络下载的dataOperation来完成下载以及图片解码。这就是大致流程,当然在这个过程中缓存管理,图片解码等都有很多可圈可点的地方,再次不再赘述,可以单独开篇讲解。

你可能感兴趣的:(SDWebImage源码阅读笔记(一))