SDWebImage 源码阅读(二)
来源:互联网 发布:重庆网络推广怎样 编辑:程序博客网 时间:2024/05/17 04:50
我们在上一篇文章介绍了 SDWebImage 框架的 UIImageView+WebCache 扩展类,主要功能就是设计了严密的判断,保证运用该框架的其他开发者能够在各种环境下获取图片。这篇文章主要是介绍 SDWebImageManager 中从缓存获取图片或者从网络下载图片。
第一部分:获取图片对应的 key
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock { // Invoking this method without a completedBlock is pointless // 调用这个方法必须实现 completedblock NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); // 下面两段代码都是为了保护 url 逻辑性 // 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 if (![url isKindOfClass:NSURL.class]) { url = nil; } // 创建一个 operation __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __weak SDWebImageCombinedOperation *weakOperation = operation; // 判断当前的url,是否包含在下载失败的url集合中,在访问failedURLs集合时要加锁,防止其他线程对其进行操作 BOOL isFailedUrl = NO; @synchronized (self.failedURLs) { isFailedUrl = [self.failedURLs containsObject:url]; } // 如果url为空 || url存在于下载失败url集合中并且不是下载失败后重新下载,那么抛出错误 if (url.absoluteString.length == 0 || (!(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 加到运行中的url集合中 @synchronized (self.runningOperations) { [self.runningOperations addObject:operation]; } // 从缓存中根据url取出key(其实key就是url的string) NSString *key = [self cacheKeyForURL:url]; // 根据key去查找对应的图片 ??? operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { ... // 详情见 第二部分}]; return operation;}
第二部分:根据 image 判断是否需要从网络获取
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { ... }];
上面的代码调用了 SDImageCache 中的 queryDiskCacheForKey: done: 方法,我们后续章节再研究这个类中的东西,我们先看看对这个方法中的 block 的处理,也就是结果的处理方法:
// 判断该任务是否已经cancel了if (operation.isCancelled) { @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } return;}// 先看 && 的后半段// 注意后面的 A || B,如果 A 为真,那就不用判断 B 了// 也就是说,如果 imageManager:shouldDownloadImageForURL: 方法没有实现,直接返回 YES// 目前我在源码中并没有看到函数的实现,所以就当 if 的后半段恒为 YES// 我们主要看 && 前面的 || 表达式// 如果缓存中没找到 image,或者需要刷新内存,就执行 if 语句if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { ... // 详情见第三部分}// 从缓存中找到 imageelse if (image) { dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !strongOperation.isCancelled) { completedBlock(image, nil, cacheType, YES, url); } }); // 将该 operation 从数组中移除 @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; }}else { // Image not in cache and download disallowed by delegate // 又没有从缓存中获取到图片,shouldDownloadImageForURL 又返回 NO,不允许下载,悲催! // 所以 image 和 error 均传入 nil 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]; }}
第三部分:初始化下载图片
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { ... }
上面判断 if 中实现的内容就是 image 需要网络获取部分,详情如下:
if (image && options & SDWebImageRefreshCached) { dispatch_main_sync_safe(^{ // 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. // 如果图片在缓存中找到,但是options中有SDWebImageRefreshCached // 那么就尝试重新下载该图片,这样是NSURLCache有机会从服务器端刷新自身缓存 completedBlock(image, nil, cacheType, YES, url); });}// download if no image or requested to refresh anyway, and download allowed by delegate// 首先定义枚举值 downloaderOptions,并根据 options 来设置 downloaderOptions// 基本上 options 和 downloaderOptions 是一一对应的,只需要注意最后一个选项 SDWebImageRefreshCachedSDWebImageDownloaderOptions 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 (image && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing // 相当于downloaderOptions = downloaderOption & ~SDWebImageDownloaderProgressiveDownload); // ~ 位运算,取反 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing // 相当于 downloaderOptions = (downloaderOptions | SDWebImageDownloaderIgnoreCachedResponse); // 因为SDWebImage有两种缓存方式,一个是SDImageCache,一个就是NSURLCache // 所以已经从SDImageCache获取了image,就忽略NSURLCache了。 downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;}// 下载图片 (图片下载部分将在后续章节解读,这里先不介绍)id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { ... // 详情见 第四部分 }];operation.cancelBlock = ^{ [subOperation cancel]; @synchronized (self.runningOperations) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } }};
第四部分:下载图片完成后 block 中的回调
图片下载部分将在后续章节讲解,这里先介绍一下图片下载完成后 block 中的内容:
__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}else if (error) { dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(nil, error, SDImageCacheTypeNone, finished, 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 从 failedURLs 中移除 if ((options & SDWebImageRetryFailed)) { @synchronized (self.failedURLs) { [self.failedURLs removeObject:url]; } } // 是否只能从内存中取出 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); // 如果需要刷新缓存,并且存在 image,并且没有新下载的 image if (options & SDWebImageRefreshCached && image && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block } // 如果有新下载的 image,并且需要对 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), ^{ // 获取处理后的图片 UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; // 将图片缓存到内存中(这里后续讲解) [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); } }); });}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]; } }}
总结
SDWebImageManager 类中主要是对图片下载和缓存方式的管理,以及对下载完成的图片,url 等信息的回调,承上启下的作用,承接了 UIImageView+WebCache 类,下接了 SDImageCache 类和 SDWebImageDownloader 类。
SDWebImage 源码解析 github 地址:https://github.com/Mayan29/SDWebImageAnalysis
遗留问题:
根据 key 去缓存中查找对应的图片
SDImageCache 类
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock
下载图片
SDWebImageDownloader 类
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock
将图片存入缓存
SDImageCache 类
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk
- SDWebImage 源码阅读(二)
- [iOS]SDWebImage 源码阅读(二)缓存
- SDWebImage 源码阅读笔记(二)
- SDWebImage 源码阅读(一)
- SDWebImage 源码阅读(三)
- SDWebImage 源码阅读(四)
- SDWebImage源码解析(二)
- SDWebImage源码剖析(二)
- [iOS]SDWebImage 源码阅读(一)
- [iOS]SDWebImage 源码阅读(三)下载
- SDWebImage 源码阅读笔记(一)
- SDWebImage 源码阅读笔记(三)
- SDWebImage 源码阅读笔记(四)
- 源码阅读:SDWebImage
- 源码阅读笔记-----SDWebImage
- SDWebImage 源码阅读笔记
- SDWebImage源码阅读
- SDWebImage源代码阅读(二)
- Python3.x和以前版本的差异
- MyBatis动态sql详解
- 约瑟夫问题
- Masonry介绍与使用实践:快速上手Autolayout
- AngularJS实际项目应用——命名规范概览
- SDWebImage 源码阅读(二)
- 位操作方法
- Two pointers (1) -- Linked List Cycle II, Rotate List
- linux 文件属性与权限(简单讲解)
- tomcat 启动失败
- button按钮onclick调用的函数名和id相同会导致找不到函数
- Mybatis框架原理
- schedule_work 工作队列
- C++幼儿园[2] - 数据类型和基本语法