美文网首页
SDWebImage源码阅读笔记(一)

SDWebImage源码阅读笔记(一)

作者: 碧玉小瑕 | 来源:发表于2020-05-03 22:49 被阅读0次

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

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

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

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

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

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

    下面进入API查看:

    发现其APIUIImageView+WebCache的类别中:

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

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

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

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

    下面开始逐行代码分析:

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

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

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

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

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

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

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

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

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

    继续向下走:

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

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

    self.sd_imageProgress.totalUnitCount = 0;

    self.sd_imageProgress.completedUnitCount = 0;

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

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

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

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

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

    展开这部分代码:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    展开queryDiskBlock:

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

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

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

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

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

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

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

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

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

    设置一个网络下载策略。

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

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

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

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

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

    逐步展开来看:

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

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

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

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

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

    展开分析:

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

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

    进入operation的构造方法:

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

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

    展开这个锁:

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

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

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

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

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

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

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

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

    而在协议方法中:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    展开分析:

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

    紧接着就是CG的天地了:

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

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

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

    进去这个方法:

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

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

    可以看出就是在

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

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

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

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

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

    展开分析:

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

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

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

    结束缓存后:

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

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

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

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

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

    从网路上借张图:

    来自SDWebImage源码分析 原

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

    相关文章

      网友评论

          本文标题:SDWebImage源码阅读笔记(一)

          本文链接:https://www.haomeiwen.com/subject/yamqghtx.html