SDWebImage解析
SDWebImage是开发中常用的网络加载图片的库,使用频率高到令人发指。本身优秀的接口封装和底层的内存管理都让开发者免去了很多麻烦,那么作为一个开发者,有必要对这么一个每天都要打交道的库有一些更深入的了解。这里我记录下自己阅读源码的心得,算是做个笔记。
|
|
而这个方法里面是直接调用
这个方法首先第一步就是检测是否用了相同的请求,在UIView的webCacheOperation category中,实现方法
|
|
来看看这个方法,SDOperationDictionary是一个普通的dictionary,当一个UIView或者其子类调用webCacheOperation方法的时候会为其延迟加载一个dictionary,由于这个property只会在运行时声明和调用,所以这里采取objc_getAssociatedObject 关联对象的方法,对于每个UIView,如果要使用SDOperationDictionary这个property,那么会在需要的时候调用。实际上整个框架里的操作都是通过一个operationDictionary来管理,添加到VIView上可以使任何UIView的子类可以直接绑定对应的operationDictionary。
|
|
这个方法为UIView动态添加一个property。
回到sd_cancelImageLoadOperationWithKey方法,从这个dictionary中取出对应key的元素,如果是数组则每一个都取消,否则直接取消(前提是实现SDWebImageOperation这个协议)这样的话当开启一个新的操作的时候不会被前面的操作所影响。
取消之后,首先如果需要的话(SDWebImageOption 这个flag中没有选择 SDWebImageDelayPlaceholder)在主线程中使用placeholder图片来暂时占据发起图片加载请求的UIView(通常为nil,但是如果有选择的话其实可以预加载图片,在还没有完成真正的图片加载之前先占据视图以免为空白或者仍为旧图片)
之后进入正式的加载图片的部分
上面关键的部分我加入了一些注释,这是在取得了image & data的情况下怎么样调用数据的处理。总的来说这个部分其实是完成三个功能:(1)创建一个新的operation指令对象 (2)赋予这个对象complete block (3)将请求加入UIView的opereation dictionary中
接下来先看如何创建一个operation对象,至于dictionary中的指令队列处理放到后面再说。
首先看看SDWebImageManager中对自己功能的描述:
The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). You can use this class directly to benefit from web image downloading with caching in another context than a UIView.
意为在UIImageView+WebCache背后,管理异步下载(SDWebImageDownloader)和图片缓存(SDImageCache)的类。实际上和它的名字本身是一样,是一个统合管理的类,构建起几个模块中间的桥梁。
|
|
如上面的代码所示,SDWebImageManager中调用方法返回一个operation对象的过程大致是(1)创建一个SDWebImageCombinedOperation对象 (2)调用[self.imageCache queryCacheOperationForKey:key done:^(UIImage cachedImage, NSData cachedData, SDImageCacheType cacheType)方法赋值operation的cacheOperation,里面包含从网络获取图片的block以及对image数据预处理和缓存的指令(3)给operation的cancelBlock赋值
到这里我们就已经是在成功获取图片的前提下进行了一系列操作,大家其实都可以明白,由于是将回调block的形式进行调用,所以这里在底层执行的顺序其实和我们现在一步步查找代码的调用顺序是反过来的,下面才是真正拿到图片的重头戏:
|
|
可以看到是直接调用另一个方法的,那我们先看这个被调用的方法:
|
|
而上面可以看到,系统自己调用的createCallback是这样的:
|
|
注意实际上调用的时候completion blocks执行的顺序其实和上面分析的顺序正好是反过来的,毕竟只有底层调用完成之后才能执行上一层的completion操作,而且也需要说明的是由于最底层的operation请求不是直接启用,而是加入到operationQueue中等待执行,所以所有的处理方法全部只能用block的形式封装起来进行传递。
整个框架在阅读起来比较麻烦,因为采用了太多的回调的形式,并且manager类联系所有其他功能类的这个结构,一开始没发现的话其实在不同类中跳来跳去十分繁琐。但是一但了解这种设计模式,是十分有好处的,并且框架中大量使用的block语法和多线程编程,十分精彩。阅读这个框架的源码确实能学到很多东西。
总的来说SDWebImage的加载策略非常直观,首先请求内存是否有URL对应的图片,如果没有就查找磁盘中的缓存;
如果缓存中都没有的话发起请求异步加载图片(当然加载的过程,设置还有对应的回调是最精髓的地方),将请求加入到待执行的请求队列中,一个个执行。直到异步加载成功之后,执行completion回调,缓存新下载的图片并更新UI(如果需要的话)。
补充说明:NSURLCache的执行策略
上面有些地方忽略说明,因为很难一两句交代清楚,这里补充说明一下NSURLCache的缓存策略:
根据官网的说明,应该遵循的流程如下:
(1) 如果请求的缓存响应不存在,直接从源加载数据
(2) 如果存在,查看是否每次需要重新验证,如果不是响应的缓存的过期则直接加载缓存数据
(3) 如果缓存的响应过期或者需要重新验证,URL加载系统发送HEAD请求到源,查看是否需要抓取新的响应
(4) 如果需要就重新请求,如果不需要就直接返回缓存的数据
前面提到的源码中调用了NSURLRequestCachePolicy的大致逻辑就是如此。
总结SDWebImage的流程
SDWebImage中一般调用sd_setImageWithURL:placeholderImage:这个方法,最终它会调用一个需要progressBlock,completionBlock的方法。
然后获取SDWebImageManager中的单例调用一个downloadImageWithURL:…方法来获取图片,先查找魂村中的记录,以URL作为索引,顺序是先查找缓存然后查找磁盘中的数据,如果有就直接使用。如果都没有那么manager就会调用加载图片的方法downloadImageWithURL:来从网络获取图片,它会调用另一个addProgressCallback:andCompletedBlock:URL:createCallback:来存储progress和completed的回调block,第一次添加时会实例化NSMutableURLRequest和SDWebImageDownloaderOperation,然后将operation加入下载队列开始异步加载,下载完成后使用图片。
本身倒是挺通俗易懂的。