一行行看SDWebImage源码(一)

276769-f7b02c377c44f9ea

地表最强

SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:
一个异步下载图片并且支持缓存的UIImageView分类.

UIImageView+WebCache

我们最常用的方法就是这个:

现在开始我们一步步地看这个方法的内部实现:

这里会调用下面这个方法:

我们看UIImageView+WebCache.h文件,我们可以发现为开发者提供了很多类似于- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder的方法.
这些方法最终都会调用- sd_setImageWithURL: placeholderImage: options: progress: completed:,这个方法可以算是核心方法,下面我们看一下- sd_setImageWithURL: placeholderImage: options: progress: completed:
方法的实现:

首先执行

接下来我们来看看[self sd_cancelCurrentImageLoad]内部是怎么执行的:


UIView+WebCacheOperation

下面我们先来看看UIView+WebCacheOperation里面都写了些什么:
UIView+WebCacheOperation这个分类提供了三个方法,用于操作绑定关系

为了方便管理和找到视图正在进行的一些操作,WebCacheOperation将每一个视图的实例和它正在进行的操作(下载和缓存的组合操作)绑定起来,实现操作和视图一一对应关系,以便可以随时拿到视图正在进行的操作,控制其取消等,如何进行绑定我们在下面分析:
UIView+WebCacheOperation.m文件内
- (NSMutableDictionary *)operationDictionary用到了中定义的两个函数:

  • objc_setAssociatedObject
  • objc_getAssociatedObject

    NSObject+AssociatedObject.h

    NSObject+AssociatedObject.m

objc_setAssociatedObject作用是对已存在的类在扩展中添加自定义的属性 ,通常推荐的做法是添加属性的key最好是static char类型的,通常来说该属性的key应该是常量唯一的.
objc_getAssociatedObject根据key获得与对象绑定的属性.

接下来我们继续探索
- sd_setImageWithURL: placeholderImage: options: progress: completed:


SDWebImageManager

-sd_setImageWithURL:forState:placeholderImage:options:completed:中,下载图片方法是位于SDWebImageManager类中- downloadImageWithURL: options:progress:completed:函数
我们看下文档是对SDWebImageManager怎么描述的

我们继续看SDWebImageManager .m
初始化方法

利用image的url生成一个缓存时需要的key,cacheKeyFilter的定义如下:
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
是一个可以返回一个字符串的block

检查一个图片是否被缓存的方法

下面两个方法比较类似,都是先根据图片的url创建对应的key

第一个方法先用BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);判断图片有没有在内存缓存中,如果图片在内存缓存中存在,就在主线程里面回调block,如果图片没有在内存缓存中就去查找是不是在磁盘缓存里面,然后在主线程里面回到block

第二个方法只查询图片是否在磁盘缓存里面,然后在主线程里面回调block

上面的方法都是用于查询图片是否在内存缓存或磁盘的缓存下面的方法可以算是SDWebImageManager核心方法:

首先我们先来看看__block__weak的区别
__block用于指明当前声明的变量在被block捕获之后,可以在block中改变变量的值.因为在block声明的同时会截获该block所使用的全部自动变量的值,这些值只在block中只有”使用权”而不具有”修改权”.而block说明符就为block提供了变量的修改权,**block不能避免循环引用**,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj,blockObj = nil

__weak是所有权修饰符,__weak本身是可以避免循环引用的问题的,但是其会导致外部对象释放之后,block内部也访问不到对象的问题,我们可以通过在block内部声明一个__strong的变量来指向weakObj,使外部既能在block内部保持住又能避免循环引用

我们再来看看SDWebImageCombinedOperation到底有一些什么内容
SDWebImageCombinedOperation它什么也不做,保存了两个东西(一个block,可以取消下载operation,一个operation,cacheOperation用来下载图片并且缓存的operation)
并且SDWebImageCombineOperation遵循协议,所以operation可以作为返回值返回

@synchronized是OC中一种方便地创建互斥锁的方式–它可以防止不同线程在同一时间执行区块的代码
self.failedURLs是一个NSSet类型的集合,里面存放的都是下载失败的图片的url,failedURLs不是NSArray类型的原因是:
在搜索一个个元素的时候NSSet比NSArray效率高,主要是它用到了一个算法hash(散列,哈希) ,比如你要存储A,一个hash算法直接就能找到A应该存储的位置;同样当你要访问A的时候,一个hash过程就能找到A存储的位置,对于NSArray,若想知道A到底在不在数组中,则需要遍历整个数据,显然效率较低了
并且NSSet里面不含有重复的元素,同一个下载失败的url只会存在一个
- (BOOL)containsObject:(ObjectType)anObject;,判断集合里面是否含有这个obj

把operation加入到self.runningOperations的数组里面,并创建一个互斥线程锁来保护这个操作
获取image的url对应的key

其实看到这里,下面牵扯的代码就会越来越越多了,会牵扯到的类也越来越多,我们先一步步地顺着往下看,然后再看涉及到的类里面写了些什么,最后再做总结,在这之前如果你对NSOperation还不够了解建议你先暂时停下看看下这篇文章NSOperation. 然后再继续往下阅读.

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;SDImageCache的一个方法,根据图片的key,异步查询磁盘缓存的方法

我们先来看下这个方法里面都有什么:
_ioQueue的定义是:@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
_ioQueue的初始化是:
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
DISPATCH_QUEUE_SERIAL代表的是创建一个串行的队列,所以_ioQueue是一个串行队列(任务一个执行完毕才执行下一个)
PS:如果你对GCD队列不太了解可以先看下GCD使用经验与技巧浅谈,然后再继续阅读额

总结:

这个方法主要完成了这些工作:1.创建一个组合Operation,是一个SDWebImageCombinedOperation对象,这个对象负责对下载operation创建和管理,同时有缓存功能,是对下载和缓存两个过程的组合。
2.先去寻找这张图片 内存缓存和磁盘缓存,这两个功能在self.imageCachequeryDiskCacheForKey: done:方法中完成,这个方法的返回值既是一个缓存operation,最终被赋给上面的OperationcacheOperation属性。在查找缓存的完成回调中的代码是重点:它会根据是否设置了SDWebImageRefreshCached
选项和代理是否支持下载决定是否要进行下载,并对下载过程中遇到NSURLCache的情况做处理,还有下载失败的处理以及下载之后进行缓存,然后查看是否设置了形变选项并调用代理的形变方法进行对图片形变处理。
3.将上面的下载方法返回的操作命名为subOperation,并在组合操作operationcancelBlock代码块中添加对subOperationcancel方法的调用。

整个大体的操作流程,就是这些,你不感觉少点啥子麽,如何下载,如何缓存(缓存只是暂时看了两个相关的方法)我们在下一篇再详细地看

1 8 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部