Asynchronous image downloader with cache support as a UIImageView category

支持图片异步下载和缓存的UIImageView分类

UIView+WebCache

  1. 最基本的方法是UIImageView+WebCache中这个方法
 - (void)sd_setImageWithURL:(nullable NSURL *)url;
复制代码
  1. 一步步走下来,会发现实际运用的是UIView+WebCache中的方法,包括UIButton+WebCache内部核心方法也是调用的下面的方法,其中SDWebImageOptions策略详细介绍可以看这里
 - (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsoperationKey:(nullable NSString *)operationKeysetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock;
复制代码
  1. 进入方法内部:先取消相关的所有下载
    //  operationKey  用来描述当前操作的关键字标识,默认值是类名字,即 @"UIImageView"NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);// 取消当前view下 跟validOperationKey有关的所有下载操作,以保证不会跟下面的操作有冲突[self sd_cancelImageLoadOperationWithKey:validOperationKey];// 通过runtime的关联对象给UIView添加属性,设置图片地址objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
复制代码

其中取消操作方法内部涉及到一个协议<SDWebImageOperation>,这个协议只有一个cancel方法,可见,这个协议就是用来取消操作的,只要遵守该协议的类,必定会有cancel方法。

@protocol SDWebImageOperation <NSObject>- (void)cancel;@end
复制代码
取消方法的具体实现:
涉及到一个字典`SDOperationsDictionary`类型为`NSMutableDictionary<NSString *, id>`,也是通过关联对象添加为UIView的属性,用来存储UIView的所有下载操作,方便之后的取消/移除
复制代码
 - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {// 从队列中取消跟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];}// 最后从字典中移除key[operationDictionary removeObjectForKey:key];}}
复制代码
  1. 如果没有设置延迟加载占位图SDWebImageDelayPlaceholder,就会先进行加载占位图,
if (!(options & SDWebImageDelayPlaceholder)) {dispatch_main_async_safe(^{// 返回主线程中进行UI设置,把占位图当成image进行图片设置,在方法内部会进行UIButton和UIImageView的判断区分[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];});}
复制代码

其中有一个宏定义,通过字符串的比较来获取主线程

#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
复制代码
  1. 判断url,url为空的情况下,直接返回错误信息
    if (url) {// check if activityView is enabled or not// 检查菊花if ([self sd_showActivityIndicatorView]) {[self sd_addActivityIndicator];}// url 存在的情况下进行的操作...} else {// url 为nil的情况下,生成错误信息,并返回         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);}});}
复制代码
  1. url不为nil的情况下,获取图片信息,并生成operation,然后存储。
// 返回的是一个遵从了SDWebImageOperation协议的NSObject的子类,目的是方便之后的取消/移除操作
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { /*完成之后的操作*/}];// 根据validOperationKey把生成的operation放入字典`SDOperationsDictionary`中,这个字典也是通过关联对象,作为UIView的一个属性。[self sd_setImageLoadOperation:operation forKey:validOperationKey];
复制代码
  1. SDInternalCompletionBlock是在UIView内部使用的completedBlock,在block中,返回获取到的图片,以及相关信息。最后在主线程中,进行UI更新并更新布局。
// weak 避免 保留环__weak __typeof(self)wself = self;id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {// block 中强引用替换,避免使用过程中被系统自动释放__strong __typeof (wself) sself = wself;// 加载完成移除菊花[sself sd_removeActivityIndicator];if (!sself) {return;}dispatch_main_async_safe(^{if (!sself) {return;}// SDWebImageAvoidAutoSetImage, 对图片进行手动设置,开发者在外面的complete里面可以对图片设置特殊效果,然后赋值ImageView.imageif (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {completedBlock(image, error, cacheType, url);return;} else if (image) {// 更新图片,内部会进行imageView或者button的判断[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];// 更新布局Layout[sself sd_setNeedsLayout];} else {// SDWebImageDelayPlaceholder 延迟加载占位图,下载完成后才会进行设置if ((options & SDWebImageDelayPlaceholder)) {[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];[sself sd_setNeedsLayout];}}// 如果有返回block,返回block和其它信息if (completedBlock && finished) {completedBlock(image, error, cacheType, url);}});}];
复制代码

总结:

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.

SDWebImageManager起一个承上启下的作用,紧密连接图片下载SDWebImageDownloader和图片缓存SDImageCache,可以直接通过这个类获取缓存中的图片。

核心方法(也是UIView+WebCache的第6步):

- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock;
复制代码

内部实现:

  1. 如果completedBlock为空,直接闪退并抛出错误信息。即,completedBlock不能为空。

    • NSAssert只有在debug状态下有效
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
复制代码
  1. 确保url是正确的,加安全验证,虽然url偶尔在字符串的情况下不报警告,但最好还是转换成NSURL类型,
if ([url isKindOfClass:NSString.class]) {url = [NSURL URLWithString:(NSString *)url];}// 防止url在某些特殊情况下(eg:NSNull)导致app闪退if (![url isKindOfClass:NSURL.class]) {url = nil;}
复制代码
  1. 首先方法要返回的是遵从了<SDWebImageOperation>协议的对象,所以声明了一个对象SDWebImageCombinedOperation,该对象遵从了协议,下面会对其属性进行一一设置。 而cancelled属性是在UIView+WebCache第3点设置的。
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
复制代码

最后需要返回operation,所以进行创建、属性赋值、返回。

// 创建一个SDWebImageCombinedOperation,加上 __block,可以让它在后续block内进行修改,__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];// 加上__weak 避免保留环__weak SDWebImageCombinedOperation *weakOperation = operation;/*对operation 进行赋值操作,最后返回*/return operation;
复制代码
  1. 再次对url进行判断,failedURLs类型是NSMutableSet<NSURL *>,是用来存储错误url的集合
    // 声明一个BOOL值,isFailedUrlBOOL isFailedUrl = NO;if (url) {// 创建一个同步锁,@synchronized{}它防止不同的线程同时执行同一段代码@synchronized (self.failedURLs) {// 错误的url都会放在failedURLs 中,判断该url是否在里面,返回并赋值isFailedUrlisFailedUrl = [self.failedURLs containsObject:url];}}// 如果url长度为0,或者 options中没有 SDWebImageRetryFailed(一直进行下载), 并且是错误的urlif (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {// 不再向下执行,直接回调completeBlock,并传递错误信息,url不存在,NSURLErrorFileDoesNotExist[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];return operation;}
复制代码
  1. runningOperations类型为NSMutableArray<SDWebImageCombinedOperation *>存储所有待执行的操作任务
@synchronized (self.runningOperations) {// 把operation 存储起来[self.runningOperations addObject:operation];}
复制代码
  1. 在缓存中查找图片,并将找到的图片的相关信息返回, 同时对operation.cacheOperation属性赋值。 (该方法是SDImageCache类的实例方法,下篇再分析)
// 根据url 返回一个本地用来缓存的标志 keyNSString *key = [self cacheKeyForURL:url];operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {   // 查询结束后执行操作   }];
复制代码
  1. 缓存中是否查找到图片,分别处理: a. 找不到图片,但是允许从网络下载,就进行网络下载
// 如果执行过程中操作取消,安全移除操作
// return 是跳出这个blockif (operation.isCancelled) {[self safelyRemoveOperationFromRunning:operation];return;}// 1. 如果不存在缓存图片,或者需要刷新缓存 2. 代理可以响应方法,或者代理直接执行该方法,即从网络下载图片// 1 和 2 是并且关系if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { //  网络下载图片
}
复制代码

b. 如果找到了缓存图片,回调图片及相关信息,操作结束,安全移除操作

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];}
复制代码

c. 缓存中找不到图片,也不允许网络下载图片:

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];
}
复制代码
  1. 对a步骤一步步分析:如果有缓存图片,同时还要求刷新缓存,那么界面先加载缓存图片,然后网络下载,下载成功之后界面加载网络图片,然后在缓存中刷新之前的缓存图片
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.// 如果在缓存中找到了图片,但是设置了SDWebImageRefreshCached,因此要NSURLCache重新从服务器下载// 先调用completeBlock后续进行网络下载[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];}
复制代码
  1. 根据SDWebImageOptions的选项对SDWebImageDownloaderOptions进行对接,一一对应,协调处理。|= 可以理解为添加 SDWebImageDownloaderOptions的详细介绍点这里
downloaderOptions |= SDWebImageDownloaderLowPriority
// 等同于
downloaderOptions = downloaderOptions | SDWebImageDownloaderLowPriority
复制代码
// downloaderOptions 默认为0SDWebImageDownloaderOptions downloaderOptions = 0;if (options & SDWebImageLowPriority)  downloaderOptions |= SDWebImageDownloaderLowPriority;// 如果需要刷新缓存,downloaderOptions强制解除SDWebImageDownloaderProgressiveDownload,并且添加SDWebImageDownloaderIgnoreCachedResponse选项if (cachedImage && options & SDWebImageRefreshCached) {// force progressive off if image already cached but forced refreshingdownloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;// ignore image read from NSURLCache if image if cached but force refreshingdownloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;}
复制代码
  1. 通过url进行网络下载图片: 每一个下载SDWebImageDownloader对象对应于一个SDWebImageDownloadToken对象,目的是用于取消/移除SDWebImageDownloader对象。 通过SDWebImageDownloader的实例方法生成一个SDWebImageDownloadToken对象。(该方法下篇分析)
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
// 图片下载完成之后的操作。
}];
复制代码

operation.cancelBlock赋值。 通过上面生成的subOperationToken来进行取消SDWebImageDownloader操作

operation.cancelBlock = ^{[self.imageDownloader cancel:subOperationToken];__strong __typeof(weakOperation) strongOperation = weakOperation;[self safelyRemoveOperationFromRunning:strongOperation];};
复制代码
  1. 操作取消或者存在网络错误的情况下:
// 操作不存在或者操作取消的情况下不做任何处理。
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {// https://github.com/rs/SDWebImage/pull/699} else if (error) {[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];// 在下面情况下(不是因为网络问题),url本身有问题的情况下,才会添加进failedURLsif (   error.code != NSURLErrorNotConnectedToInternet&& error.code != NSURLErrorCancelled&& error.code != NSURLErrorTimedOut&& error.code != NSURLErrorInternationalRoamingOff&& error.code != NSURLErrorDataNotAllowed&& error.code != NSURLErrorCannotFindHost&& error.code != NSURLErrorCannotConnectToHost) {// 跟前面第4点对应,failedURLs添加错误的url@synchronized (self.failedURLs) {[self.failedURLs addObject:url];}}}
复制代码
  1. 成功情况下: a. 对应于第8点,刷新缓存,并且图片下载失败的情况下:
// 下载选项,允许失败后重新下载,if ((options & SDWebImageRetryFailed)) {// 重新下载,得保证 url 是正确的,不在failedURLs里面@synchronized (self.failedURLs) {[self.failedURLs removeObject:url];}}// 是否允许磁盘缓存BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);// 没有下载图片的情况下,不能刷新缓存if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {// Image refresh hit the NSURLCache cache, do not call the completion block// 对应于第8点,已经返回completeBlock,这里不做任何处理。}
复制代码

b. 1. 有下载图片 2. 界面上下载图片尚未赋值,或者策略允许图片变换 3. 代理响应了图片变换操作 1,2,3 是并且关系。 图片先进行变换,然后缓存,最后回调

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];// pass nil if the image was transformed, so we can recalculate the data from the image// 如果图片变换成功,imageData传nil,这样在缓存图片的时候,可以重新计算data大小,反之,就传downloadedData[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];});}
复制代码

c. 不对图片进行处理,直接缓存图片并回调。

else {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];}
复制代码

总结:


图片加载之SDWebImage(上)相关推荐

  1. ios 图片加载内存尺寸_iOS内存分析上-图片加载内存分析

    简介 对于大多数App来说,内存占用主要就是图片.本文将从实用的角度分析,iOS图片的内存占用.测量.优化等. iOS内存-有什么影响 在移动操作系统设备中,是不能像PC一样进行内存swap的,而随着 ...

  2. Universal-Image-Loader解析(二)——DisplayImageOptions的详细配置与简单的图片加载...

    在使用这个框架的时候,我们必须要配置一个DisplayImageOptions对象来作为ImageLoader.getInstance().displayImage()中的参数,所以很有必要讲解这个对 ...

  3. DisplayImageOptions的详细配置与简单的图片加载

    在使用这个框架的时候,我们必须要配置一个DisplayImageOptions对象来作为ImageLoader.getInstance().displayImage()中的参数,所以很有必要讲解这个对 ...

  4. SDWebImage使用,图片加载和缓存

    本文转载至 http://blog.163.com/wzi_xiang/blog/static/659829612012111402812726/ 清除缓存: [[SDImageCache share ...

  5. SDWebImage 图片加载和缓存

    SDWebImage github : https://github.com/rs/SDWebImage 这个类库提供一个UIImageView类别以支持加载来自网络的远程图片.具有缓存管理.异步下载 ...

  6. 小程序一次性上传多个本地图片,上拉加载照片以及图片加载延迟解决之道

    一:小程序之一次性上传多个本地相片 最近由于项目需要所以学了下小程序,也做了一些东西,随后便有了以下的一些总结了,现在说说如何使用小程序一次性上传多个本地相片. 问题描述 最近做项目的时候要实现一个上 ...

  7. Android踩坑日记:使用Fesco图片加载库在GridView上的卡顿优化

    1,fresco是一个强大的图片加载库 2,fresco设计了一个叫做image pipeline(图片管道)的模块,它负责从从网络,从本地文件系统,从本地资源加载图片,为了最大限度节约资源和cpu时 ...

  8. SDWebImage 图片加载失败

    今天在项目中,使用SDWebImage加载图片,总有图片加载不出来.使用 [self.centerIV sd_setImageWithURL:[NSURL URLWithString:self.sou ...

  9. iOS开发:利用SDWebImage实现图片加载与缓存

    iOS开发:利用SDWebImage实现图片加载与缓存 SDWebImage是一套开源框架,这个类库提供一个UIImageView类别以支持加载来自网络的远程图片.具有缓存管理.异步下载.同一个URL ...

最新文章

  1. cnetos7安装docker V1.0
  2. OpenCV实践之路——人脸检测(C++/Python) 【转】
  3. liunxC下零碎知识点的总结
  4. bfs理解——hdu6386好题
  5. bzoj 4573: [Zjoi2016]大森林
  6. Trick(十一)—— list of lists 每一个属性列的获取
  7. Ubuntu常见错误合集——持续更新
  8. 百度全景地图 -(街景)_百度地图VR全景,世界触手可及
  9. android获取农历时间,android 日历(带提醒、日程、阴历转换)
  10. 安装部署rabbitmq报错——已解决
  11. Cassandra CQL使用详解
  12. 程序大咖的博客集锦_更新Unity3d
  13. 高德地图SDK在API 31以上崩溃的问题
  14. 宽带服务器盒信号灯红色闪烁,移动盒子光信号闪红灯怎么回事
  15. 统计学之大数定律、小数法则
  16. 【技术分享】美团外卖的商业变现的技术思考和实践
  17. matlab中==、~=、的含义
  18. 双cpu适合安装什么服务器系统吗,服务器双CPU使用时的注意事项
  19. 快充新宠-PD快充墙插面板
  20. 2021-06-16 节点电压为极坐标下的牛顿-拉夫逊法潮流计算学习

热门文章

  1. UA MATH636 信息论2 数据压缩
  2. MongoDB基本概念学习 - 文档
  3. SQL Server 临时表
  4. Windows 内核数据结构学习总结
  5. celery 学习笔记 01-介绍
  6. NLTK与NLP原理及基础
  7. 在虚拟机环境下,电脑间拷贝配置好的伪分布式Hadoop环境,出现namenode不能启动的问题!...
  8. vsftp 安装日志
  9. Codeforces Round #183 (Div. 2) C
  10. World Final 2012