SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:
一个异步下载图片并且支持缓存的UIImageView分类.
#import <Foundation/Foundation.h> #import "SDWebImageCompat.h" typedef NS_ENUM(NSInteger, SDImageCacheType) { /** * The image wasn‘t available the SDWebImage caches, but was downloaded from the web. */ SDImageCacheTypeNone,//该图像是不可用的SDWebImage缓存,但是从网上下载的. /** * The image was obtained from the disk cache. */ SDImageCacheTypeDisk,//图像从磁盘高速缓存获得. /** * The image was obtained from the memory cache. */ SDImageCacheTypeMemory//图像从存储器高速缓存获得. };
UIImageView+WebCache
我们最最最常用的方法就是这个:
[self.icon sd_setImageWithURL:nil placeholderImage:nil];
现在我们一步一步的来看这个方法内部的实现:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; }
这里会调用这个方法:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; }
我们看UIImageView+WebCache.h文件,我们可以发现为开发者提供了很多类似于
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder { } 如下就是一些例子
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; }
这些方法最终都会调用如下方法
- (void)sd_setImageWithURL: placeholderImage:options: progress: completed:
这个方法算是核心方法,下面我们来看看这段代码的实现
第一句执行的代码
//移除UIImageView当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行者一行代码 [self sd_cancelCurrentImageLoad];
接下来我们来看看 [self sd_cancelCurrentImageLoad]; 里面是怎么实现的
先调用
- (void)sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]; }
然后调用#import "UIView+WebCacheOperation.h"里面的如下方法
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key
UIView+WebCacheOperation
我们来看看这个分类都写了什么
这个分类中提供了三个方法,用于绑定操作关系
第一个方法:
/** * set the image load operation (storage in a UIView based dictiongary) * 设置图片加载操作 (存储在和UIView做绑定的字典中) * @param operation the opreation * @param key key for storing the opreation */ - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key ;
第二个方法:
/** * cancel all operations for the current UIView and key * 用这个key找到当前UIView上的所有操作并取消 * @param key key for identifying the operations */ - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key ;
第三个方法:
/** * Just remove the operations corresponding to the current UIView and key * 根据key移除操作 * @param key key key for identifying the operations */ - (void)sd_removeImageLoadOperationWithKey:(NSString *)key
为了方便管理和找到视图正在进行的一些操作,webCacheOperations,将每一个视图的实例和它正在进行的操作(下载和缓存的组合操作)绑定起来,实现操作和视图一一对应关系,以便可以随时拿到视图正在进行的操作,控制其取消等,如何进行绑定我们在下面分析:
在UIView+WebCacheOperation.m文件内,
- (NSMutableDictionary *)operationDictionary 用到了 objc/runtime 定义的两个函数
1.objc_getAssociatedObject
2.objc_setAssociatedObject
NSObject+AssociatedObject.h
@interface NSObject (Associatedobject) @property(nonatomic,strong )id associatedObject; @end
NSObject+AssociatedObject.m
objc_setAssociatedObject 的作用是对已存在的类在扩展中添加自定义的属性, 通常推荐的做法是添加属性的key最好是static char类型的通常来说该属性的key应该是常量唯一的.
objc_getAssociatedObject根据key获得以对象绑定的属性
- (NSMutableDictionary *)operationDictionary { /* 这个loadoperationkey 的定义是:static char loadoperationkey; 它对应的绑定在UIView的属性是NSMutableDictionary(NSMutableDictionary类型) operationDictionary的value是操作,key是针对不同类型视图和不同类型操作设定的字符串 注意:&一元运算符是右操作对象的地址(&loadoperationkey)返回static char loadoperationkey的. */ NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); if (operations) { //如果可以查到operations,就return,反正给视图绑定一个新的,空的operations字典 return operations; } operations = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return operations; }
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key { // Cancel in progress downloader from queue //取消正在下载的队列 NSMutableDictionary *operationDictionary = [self operationDictionary]; //如果operationDictionary可以取到,根据key可以得到与视图相关的操作,取消他们, id operations = [operationDictionary objectForKey:key]; if (operations) { if ([operations isKindOfClass:[NSArray class]]) { for (id <SDWebImageOperation> operation in operations) { if (operation) { [operation cancel]; } } } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ [(id<SDWebImageOperation>) operations cancel]; } [operationDictionary removeObjectForKey:key]; } }
接下来,我们继续探讨
- (void)sd_setImageWithURL: placeholderImage:options: progress: completed:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { //移除UIImageView当前绑定的操作.当TableView的cell包含的UIImageView被重用的时候首先执行者一行代码 [self sd_cancelCurrentImageLoad]; //将url作为属性绑定到imageview上,用static char imageURLKey作为key objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); /* #define dispatch_main_async_safe(block) if ([NSThread isMainThread]) { block(); } else { dispatch_async(dispatch_get_main_queue(), block); } */ //这是一个宏定义,因为图像的绘制只能在主线程完成,所以define dispatch_main_async_safe //为了保证block在主函数上. if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ //设置imageview的placeHolder self.image = placeholder; }); } if (url) { // check if activityView is enabled or not //检查是否通过setshowActivityIndicatorView:方法设置了显示正在加载指示器,如果设置了 if ([self showActivityIndicatorView]) { [self addActivityIndicator]; } __weak __typeof(self)wself = self; //下载的核心方法 id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { //移除加载指示器 [wself removeActivityIndicator]; //如果imageview不存在了,就return停止操作 if (!wself) return; dispatch_main_sync_safe(^{ if (!wself) return; /*SDWebImageAvoidAutoSetImage ,默认情况下图片会在下载完毕后自动添加给imageview */ if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } /*如果后两个条件至少有一个满足,那么久直接将image赋值给当前的imageView 并调用setNeedsLayout */ else if (image) { wself.image = image; [wself setNeedsLayout]; } else //image为空,并设置了延迟占位图,会将占位图设置为最终的image,并将其标记为需要重新布局 { if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; //为imageView绑定新的操作,以为把之前的操作取消了 [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"]; } else { //判断url不存在,移除加载指示器,执行完成回调,传递错误信息 dispatch_main_async_safe(^{ [self removeActivityIndicator]; NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; if (completedBlock) { completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } }
SDWebImageManager
- (void)sd_setImageWithURL: placeholderImage:options: progress: completed:下载图片是位于SDWebImageManager中的如下方法中
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
我们来看看下列文档对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. * * Here is a simple example of how to use SDWebImageManager: * * @code * 概述了SDWebImageManager的作用,其实UIImageView+WebCache这个分类背后执行的操作就是SDWebImageManager. //这是个简单的列子 SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager downloadImageWithURL:imageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; * @endcode */ /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. * 如果图片不存在缓存中,就根据给定url下载图片,否则返回缓存中的图片 * * @param url The URL to the image * @param options A mask to specify options to use for this request * @param progressBlock A block called while image is downloading * @param completedBlock A block called when operation has been completed. * * This parameter is required. * //第一个参数是必须的.就是image的url * This block has no return value and takes the requested UIImage as first parameter. * //第二个参数options 你可以定制各种各样的操作 * In case of error the image parameter is nil and the second parameter may contain an NSError. * 第三个参数是一个回调block,用于图片下载过程中的回调 * 是一个枚举 下载图片的类型 从网络下载,从本地缓存加载..... * The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache * or from the memory cache or from the network. * 最后一个参数是一个下载完成的回调,会在图片下载后完成回调 * The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is * downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the * block is called a last time with the full image and the last parameter set to YES. * 返回值是一个NSObject类,并且NSObject这个类遵守一个协议SDWebImageOperation. * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation */ - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
SDWebImageManager.m中
初始化方法:
//初始化方法 - (id)init { if ((self = [super init])) { //赋值 _imageCache = [self createCache]; //获取一个SDWebImageDownloader单例 _imageDownloader = [SDWebImageDownloader sharedDownloader]; //新建一个SDWebImageDownloader可变集合来存储下载失败的url _failedURLs = [NSMutableSet new]; //新建一个用来存储下载Operations的可变数组 _runningOperations = [NSMutableArray new]; } return self; }
利用image的url生成一个缓存时需要的key,failedURLs
failedURLs的定义如下:
@property (strong, nonatomic) NSMutableSet *failedURLs;
是一个可以返回一个字符串的block.
//如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key //如果为空,那么直接返回url的string内容,当做key - (NSString *)cacheKeyForURL:(NSURL *)url { if (self.cacheKeyFilter) { return self.cacheKeyFilter(url); } else { return [url absoluteString]; } }
检查一个图片是否被缓存的方法
//检查一个图片是否被缓存的方法 - (BOOL)cachedImageExistsForURL:(NSURL *)url { //调用上面的方法取到image的url对应的key NSString *key = [self cacheKeyForURL:url]; //首先检测内存缓存中是否存在这张照片,如果有,直接返回yes if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES; //如果内存缓存里面没有这张图片,那么久调用diskImageExistsWithKey这个方法去硬盘找 return [self.imageCache diskImageExistsWithKey:key]; } //检测硬盘里是否缓存了图片 - (BOOL)diskImageExistsForURL:(NSURL *)url { NSString *key = [self cacheKeyForURL:url]; return [self.imageCache diskImageExistsWithKey:key]; }
下面两个方法比较类似,都是先根据图片的url创建对应的key
第一个方法先用
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
判断图片有没有在内存缓存中,如果图片在内存缓存中存在,就在主线程里面回调block,如果图片没有在内存缓存中就去查找是不是在磁盘缓存里面,然后在主线程里面回到block
第二个方法只查询图片是否在磁盘缓存里面,然后在主线程里面回调block
//第二个方法只查询图片是否在磁盘缓存里面,然后在主线程里面回调block - (void)cachedImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock { NSString *key = [self cacheKeyForURL:url]; BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil); if (isInMemoryCache) { // making sure we call the completion block on the main queue dispatch_async(dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(YES); } }); return; } [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch if (completionBlock) { completionBlock(isInDiskCache); } }]; } - (void)diskImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock { NSString *key = [self cacheKeyForURL:url]; [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch if (completionBlock) { completionBlock(isInDiskCache); } }]; }
上面的方法都是用于查询图片是否在内存缓存或磁盘的缓存下面的方法可以算是SDWebImageManager
核心方法:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
//防止开发中传入 NSString 类型的url,如果url的类型 NSString,就给转换成NSURL类型 if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; } // Prevents app crashing on argument type error like sending NSNull instead of NSURL //如果转换成NSURL失败,就把传入的url置为nil下载停止 if (![url isKindOfClass:NSURL.class]) { url = nil; }
首先我们来看看__block和__weak的区别
__block用于指明当前的变量在被block捕获之后,可以在block中改变变量的值,因为在block声明的同时会截获block 使用的全部自动变量的值
这些值只在block中只有"使用权"而不具有"修改权".而block说明符就为block提供了变量的修改权,block不能避免循环引用**,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj,blockObj = nil
__weak
是所有权修饰符,__weak
本身是可以避免循环引用的问题的,但是其会导致外部对象释放之后,block内部也访问不到对象的问题,我们可以通过在block内部声明一个__strong
的变量来指向weakObj,使外部既能在block内部保持住又能避免循环引用
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __weak SDWebImageCombinedOperation *weakOperation = operation;
我们再来看看
SDWebImageCombinedOperation
到底有一些什么内容SDWebImageCombinedOperation
它什么也不做,保存了两个东西(一个block,可以取消下载operation,一个operation,cacheOperation用来下载图片并且缓存的operation)
并且SDWebImageCombineOperation
遵循<SDWebImageOperation>
协议,所以operation
可以作为返回值返回
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation> @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; @property (strong, nonatomic) NSOperation *cacheOperation; @end @implementation SDWebImageCombinedOperation - (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock { // check if the operation is already cancelled, then we just call the cancelBlock if (self.isCancelled) { if (cancelBlock) { cancelBlock(); } _cancelBlock = nil; // don‘t forget to nil the cancelBlock, otherwise we will get crashes } else { _cancelBlock = [cancelBlock copy]; } } - (void)cancel { self.cancelled = YES; if (self.cacheOperation) { [self.cacheOperation cancel]; self.cacheOperation = nil; } if (self.cancelBlock) { self.cancelBlock(); // TODO: this is a temporary fix to #809. // Until we can figure the exact cause of the crash, going with the ivar instead of the setter // self.cancelBlock = nil; _cancelBlock = nil; } } @end
@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
//创建一个互斥锁防止现有的别的线程修改failedURLs //判断这个url是否是fail过的,如果url failed过的那么让isfailedURLs就是ture BOOL isFailedUrl = NO; @synchronized (self.failedURLs) { isFailedUrl = [self.failedURLs containsObject:url]; } //如果url不存在那么久直接返回一个block,如果url存在,那么继续 //!(options & SDWebImageRetryFailed)之前就提过一个类似的了.它的意思是这个operation和SDWebImageRetryFailed相同,如果不相同,且isFailedUrl是ture,,那就回调一个error 的block if (!url || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { dispatch_main_sync_safe(^{ NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]; completedBlock(nil, error, SDImageCacheTypeNone, YES, url); }); return operation; }
把operation加入到self.runningOperations的数组里面,并创建一个互斥线程锁来保护这个操作
获取image的url对应的key
把operation加入到self.runningOperations的数组里面,并创建一个互斥线程锁来保护这个操作 获取image的url对应的key
其实看到这里,下面牵扯的代码就会越来越越多了,会牵扯到的类也越来越多,我们先一步步地顺着往下看,然后再看涉及到的类里面写了些什么,最后再做总结.
- (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
是一个串行队列(任务一个执行完毕才执行下一个)
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { if (!doneBlock) { return nil; } if (!key) { doneBlock(nil, SDImageCacheTypeNone); return nil; } // 1.首先查看内存缓存,如果查找到,则直接调用doneBlock并返回 UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil; } //2.如果内存中没有,则在磁盘中查找,如果找到,则将其放到内存缓存中,并调用doneBlock回调 NSOperation *operation = [NSOperation new]; //在ioQueue中串行处理所有磁盘缓存 dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } //创建自动释放池,内存及时释放 @autoreleasepool { //根据图片的url对应的key去磁盘缓存中查找图片 UIImage *diskImage = [self diskImageForKey:key]; //如果可以在磁盘中查找到image,并且self.shouldCacheImagesInMemory = YES(默认是YES,if memory cache is enabled)就将image储存到内存缓存中 if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); //self.memCache是NSCache创建的一个对象,下面的方法是NSCache储存对象的方法,如果你对cost的作用不太了解可以看我另外一篇文章NSCache [self.memCache setObject:diskImage forKey:key cost:cost]; } //最后在主线程里面调用doneBlock返回 dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); } }); return operation;
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { if (operation.isCancelled) { @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } return; } //条件1:在缓存中没有找到图片或者options选项里面包含了SDWebImageRefreshCached(这两项都需要进行请求网络图片的) //条件2:代理允许下载,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许 if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { //如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片 if (image && options & SDWebImageRefreshCached) { dispatch_main_sync_safe(^{ // 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新 completedBlock(image, nil, cacheType, YES, url); }); } // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载 SDWebImageDownloaderOptions downloaderOptions = 0; //开始各种options的判断 if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; if (image && options & SDWebImageRefreshCached) { // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } //创建下载操作,先使用self.imageDownloader下载 id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (!strongOperation || strongOperation.isCancelled) { // Do nothing if the operation was cancelled //如果操作取消了,不做任何事情 // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data //如果我们调用completedBlock,这个block会和另外一个completedBlock争夺一个对象,因此这个block被调用后会覆盖新的数据 } else if (error) { //进行完成回调 dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(nil, error, SDImageCacheTypeNone, finished, url); } }); //将url添加到失败列表里面 if ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost) { @synchronized (self.failedURLs) { [self.failedURLs addObject:url]; } } } else { //如果设置了下载失败重试,将url从失败列表中去掉 if ((options & SDWebImageRetryFailed)) { @synchronized (self.failedURLs) { [self.failedURLs removeObject:url]; } } BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); //options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功 if (options & SDWebImageRefreshCached && image && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block // 图片刷新遇到了NSSURLCache中有缓存的状况,不调用完成回调。 } //图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现 else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { //全局队列异步执行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ //调用代理方法完成图片transform UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; //对已经transform的图片进行缓存 [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk]; } //主线程执行完成回调 dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url); } }); }); } //如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存 else { if (downloadedImage && finished) { [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } //主线程完成回调 dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url); } }); } } if (finished) { // 从正在进行的操作列表中移除这组合操作 @synchronized (self.runningOperations) { if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } } } }]; //设置组合操作取消得得回调 operation.cancelBlock = ^{ [subOperation cancel]; @synchronized (self.runningOperations) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } } }; } //处理其他情况 //case1.在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项 满足至少一项) else if (image) { //完成回调 dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !strongOperation.isCancelled) { completedBlock(image, nil, cacheType, YES, url); } }); //从正在进行的操作列表中移除组合操作 @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } } //case2:缓存中没有扎到图片且代理不允许下载 else { //主线程执行完成回调 dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !weakOperation.isCancelled) { completedBlock(nil, nil, SDImageCacheTypeNone, YES, url); } }); //从正在执行的操作列表中移除组合操作 @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } } }];
总结: 这个方法主要完成了这些工作:1.创建一个组合Operation,是一个SDWebImageCombinedOperation对象,这个对象负责对下载operation创建和管理,同时有缓存功能,是对下载和缓存两个过程的组合。 2.先去寻找这张图片 内存缓存和磁盘缓存,这两个功能在self.imageCache的queryDiskCacheForKey: done:方法中完成,这个方法的返回值既是一个缓存operation,最终被赋给上面的Operation的cacheOperation属性。在查找缓存的完成回调中的代码是重点:它会根据是否设置了SDWebImageRefreshCached 选项和代理是否支持下载决定是否要进行下载,并对下载过程中遇到NSURLCache的情况做处理,还有下载失败的处理以及下载之后进行缓存,然后查看是否设置了形变选项并调用代理的形变方法进行对图片形变处理。 3.将上面的下载方法返回的操作命名为subOperation,并在组合操作operation的cancelBlock代码块中添加对subOperation的cancel方法的调用。