ios SDWebImage方法
洲洲哥 人气:0引言
在着手写第二篇的时候,发现这个SDWebimage确实吧NSOperation用的太美了。确实可能帮你理解NSOperation
和NSOperationQueue
,当然还有Block的队列。还有一个GCD
。
各位看官在看的时候可以着重的看看他的operatinQueue
的队列。看看是怎么添加到队列的以及是怎么移除队列。在后面的文章就会提到他是怎么执行的。 还要注重看的就是以前用的NSURLConnection
而现在用的NSURLSession
下来了
最近在面试,发现有人问这个组件,所有读一读,应付一下面试吧!!
源码解析
废话不多说看源码。
- 1:在组件中提供了很多类似这样的方法
- (void)sd_setImageWithURL:(nullable NSURL *)url; - (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- 2:于是乎所有的方法都会调用下面的这个方法
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options operationKey:(nullable NSString *)operationKey setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock
- 3:下面具体阅读代码 第一行执行的代码
//********1: 所有的设置控件图片都是经过该方法******* NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); //********2: 取消当前控件正在operations的队列******* [self sd_cancelImageLoadOperationWithKey:validOperationKey];
解析:
1.在第一行创建了validOperationKey
的operationKey,是以当前扩展的类名命名。
2.执行sd_cancelImageLoadOperationWithKey
方法
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key { // Cancel in progress downloader from queue /** * 在该对象中,用runtime手动的添加一个字典属性。 ### 说一下这里的operationDictionary ### 该字典的value是下载的操作,然而key是对视图和操作来做的标识字符串 */ SDOperationsDictionary *operationDictionary = [self operationDictionary]; /* * 在生成字典中的都是新的,所有都没有数据 */ id operations = operationDictionary[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]; } }
字典操作
在看一下去字典操作[self operationDictionary]
- (SDOperationsDictionary *)operationDictionary { /** ### 这里有一个疑问? 这样创建出来的字典每一次都是新的 虽然用了 static char loadOperationKey,但是没一次创建的地址都是新的,并且该字典还是空的。 创建完成之后都直接返回了。不知道每次创建的都是新的并且还是空的字典,这样的开销会很大的??!!!,也希望各位看官给予解答啊??? */ SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); // 创建成功返回该字典,直接把该字典当做属性返回 if (operations) { return operations; } operations = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return operations; }
看到这里我们在返回继续看 这个函数的代码有点长,耐心看完啊。。。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options operationKey:(nullable NSString *)operationKey setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock { //********1: 所有的设置控件图片都是经过该方法******* NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); /**2: 取消当前控件正在operations的队列 * 至于为什么在该控件下载前都要清空该控件对应的所有的下载队列? * 可能是,如果要给控件赋值新的图片的话,之前所有的操作都和当前的操作无关,所有就取消吧 *******/ [self sd_cancelImageLoadOperationWithKey:validOperationKey]; objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); /**3:设置占位图片*/ /**这里的意思就是options参数不是SDWebImageDelayPlaceholder,就执行以下操作 */ if (!(options & SDWebImageDelayPlaceholder)) { /** * 注意这的里宏定义 * #ifndef dispatch_main_async_safe * #define dispatch_main_async_safe(block)\ * if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\ * block();\ * } else {\ * dispatch_async(dispatch_get_main_queue(), block);\ * } * #endif 可以看出都把block方法主线程中去执行。 应为在系统里,只能有一个线程去执行UI的更新,那就是主线程。 如果能在其他线程更新,那这世界就乱了,像了解 更仔细的问题可以关注www.osjoin.com查看是为什么! */ dispatch_main_async_safe(^{ /**设置占位图placeholder*/ [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock]; }); } /**4:菊花提示*/ if (url) { // check if activityView is enabled or not if ([self sd_showActivityIndicatorView]) { [self sd_addActivityIndicator]; } __weak __typeof(self)wself = self; /**5:下载图片 * 进入下载图片最重要的函数也是核心的函数了 这个函数关联的函数较多,先简单过一下。 */ id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { __strong __typeof (wself) sself = wself; [sself sd_removeActivityIndicator]; if (!sself) { return; } dispatch_main_async_safe(^{ if (!sself) { return; } if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; /**将行的下操作放到uiview的下载队列中的自定义的字典中去*/ [self sd_setImageLoadOperation:operation forKey:validOperationKey]; } else { dispatch_main_async_safe(^{ [self sd_removeActivityIndicator]; if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } }
看一下调用下载函数前的实例化过程
这个loadImageWithURL函数在SDWebImageManager,并且是用单列调用,
+ (nonnull instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (nonnull instancetype)init { /** ###此处有其他重要配置,下一篇文章解读 ###此处有其他重要配置,下一篇文章解读 ###此处有其他重要配置,下一篇文章解读 */ SDImageCache *cache = [SDImageCache sharedImageCache]; SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; return [self initWithCache:cache downloader:downloader]; } - (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { if ((self = [super init])) { _imageCache = cache; _imageDownloader = downloader; /**这里的failedURLS是NSSet也就没重复的属性*/ _failedURLs = [NSMutableSet new]; _runningOperations = [NSMutableArray new]; } return self; }
上面都是初始化的操作,然后看下面的函数
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock { // Invoking this method without a completedBlock is pointless NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. /** 这里防止用户输入的类型错误,转换一下 */ if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; } // Prevents app crashing on argument type error like sending NSNull instead of NSURL /*! 如果转换失败,url为nil 返回 */ if (![url isKindOfClass:NSURL.class]) { url = nil; } /**5.1:搞一个新的下载队列*/ /*! 这里就又__block和__weak的用法区别 这里简单说说一下 1:__block,在block函数里可以修改和阅读 2:__weak可以避免循环引用,在给他设置新数据的时候,设置方法既不保留新值,也不释放旧值 */ /*! 说一下SDWebImageCombinedOperation 他拥有了一个缓存队列和一个能取消执行的block,并且还遵守了SDWebImageOperation一个协议 */ __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __weak SDWebImageCombinedOperation *weakOperation = operation; /**5.2判断URL是否是下载失败的url*/ BOOL isFailedUrl = NO; if (url) { /*! 创建一个互斥锁防止有其他线程同时修改该对象 */ @synchronized (self.failedURLs) { isFailedUrl = [self.failedURLs containsObject:url]; } } /** * 5.3url不存在或者下载失败的url 则不在继续下载队列 * 返回一个completedBlock */ if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url]; return operation; } /**5.4把url添加在下载队列 把operation加入到self.runningOperations的数组里面, 并创建一个互斥线程锁来保护这个操作 */ @synchronized (self.runningOperations) { [self.runningOperations addObject:operation]; } /*! 获取image的url对应的key */ NSString *key = [self cacheKeyForURL:url]; /**5.5快速查找***缓存*****/ operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { /** * 这里的状态改变,在sd_cancelImageLoadOperationWithKey方法中调用代理函数时会改变该状态cancel * 如果该队列是取消状态,直接从下载队列中移除,此处有一个安全锁 */ if (operation.isCancelled) { [self safelyRemoveOperationFromRunning:operation]; return; } /**下载完成之后执行图片转换处理和缓存操作**/ //条件1:在缓存中没有找到图片或者options选项里面包含了SDWebImageRefreshCached(这两项都需要进行请求网络图片的) //条件2:代理允许下载,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许 if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { //如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片 if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. /** 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新 dispatch_main_async_safe(^{ if (operation && !operation.isCancelled && completionBlock) { completionBlock(image, data, error, cacheType, finished, url); } }); */ [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; } // 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载 // download if no image or requested to refresh anyway, and download allowed by delegate SDWebImageDownloaderOptions downloaderOptions = 0; 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 (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; if (cachedImage && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing // 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } /**如果在缓存和硬盘上都没查到url对应的图片 ***则进行图片下载 */ /*! 进入下载操作就是2.2中的操作了*/ SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { __strong __typeof(weakOperation) strongOperation = weakOperation; /*! 如果为取消状态,啥也不错,闲着 */ if (!strongOperation || strongOperation.isCancelled) { // Do nothing if the operation was cancelled // 不用做任何事情,如果是取消状态 // See #699 for more details // 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) { //进行完成回调 [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url]; if ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost) { //将失败的url添加到failedURLS的set中去 @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 && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if ( //图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现 downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) &&[self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)] ) { /** * dispatch_get_global_queue创建的一个//全局队列异步队列执行 */ 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]; // pass nil if the image was transformed, so we can recalculate the data from the image //对已经transform的图片进行缓存 [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil]; } /*! 回到主线的调度 */ [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; }); } else { //如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存 if (downloadedImage && finished) { [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil]; } /*! 回到主线的调度 */ [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; } } /** * 从正在进行的操作列表中移除这组合操作 * 此处有一个安全锁保证线程安全 */ if (finished) { [self safelyRemoveOperationFromRunning:strongOperation]; } }]; /**取消的回调*/ operation.cancelBlock = ^{ [self.imageDownloader cancel:subOperationToken]; __strong __typeof(weakOperation) strongOperation = weakOperation; [self safelyRemoveOperationFromRunning:strongOperation]; }; //在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项 满足至少一项) } else if (cachedImage) { __strong __typeof(weakOperation) strongOperation = weakOperation; [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; } else { //缓存中没有扎到图片且代理不允许下载 // Image not in cache and download disallowed by delegate __strong __typeof(weakOperation) strongOperation = weakOperation; [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url]; [self safelyRemoveOperationFromRunning:operation]; } }]; return operation; }
这个函数就进入了SDWebimage缓存的策略了。
先说一下他的这一个策略缓存。
*1:大家都是SDWebiamge都是先从缓存上查找,如果有就直接显示
*2:如果不存在就在沙盒中查找
- *2.1如果存在,则把沙盒中的图片添加到imageCache中,取出显示
- *2.2 如果不存在在显示占位图,根据URL在operationCache是否存在下载操作
*2.2.1 如果存在,说明该图片正在下载。
*2.2.2如果不存在,创建图片下载操作,放到operationCache中
- * 2.3 下载完成,将当前操作队列从operationCache中移除。并将下载的图片的添加在imageCache中。显示
先慢慢体会一下。。。(停留30秒)
开始进入查找函数
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock { /**从缓存中查找图片开始*/ /*! 检查key是否为空(URL) */ if (!key) { if (doneBlock) { doneBlock(nil, nil, SDImageCacheTypeNone); } return nil; } // 先检查缓存--内存上的数据 // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { /**从内存在检查到有图片**/ NSData *diskData = nil; if ([image isGIF]) { diskData = [self diskImageDataBySearchingAllPathsForKey:key]; } /**如果有直接返回在view上显示*/ if (doneBlock) { doneBlock(image, diskData, SDImageCacheTypeMemory); } return nil; } /**如果内存上没有数据,则在硬盘上查找,如果找到了该图片,就放到缓存上用doneBlock完成回调**/ /*! 这一块创建了异步队列 这里的self.ioQueue是这样定义的 ****@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue; ****这里开始了串行的队列去处理硬盘上的缓存 */ NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ /**如果是取消的 就直接返回*/ if (operation.isCancelled) { // do not call the completion if cancelled return; } /*! 开了手动释放池 */ @autoreleasepool { /**从磁盘中读取图片*/ /*! 根据url去硬盘上查找数据 */ NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key]; UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.config.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); /**如果在硬盘中读取到图片,则把沙盒中的图片放到Cache中*/ //self.memCache是NSCache创建的一个对象 [self.memCache setObject:diskImage forKey:key cost:cost]; } if (doneBlock) { /*! 在主线程放回数据 */ dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, diskData, 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; } 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.先调用
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
2.设置图片
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options operationKey:(nullable NSString *)operationKey setImageBlock:(nullable SDSetImageBlock)setImageBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock
- 2.1 取消该控件对应的之前的所有的下载操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
- 2.2 开始根据图片的url做为key去查找
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock
2.2.1 查找内存和硬盘上的缓存
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- 2.3 创建下载队列下载图片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- 2.4 最后将进行的操作,放到view对应的opationsDicaionary的字典中。记录当前的操作队列
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key
加载全部内容