这个开源框架的主要作用就是:一个异步下载图片并且支持缓存的 `UIImageView'分类

框架中最常用的方法:

[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

  

框架中还有'UIButton'的分类,可以给'UIButton'异步加载图片,不过没有'UIImageView'分类中的这个方法常用。

接下来我们就以 `UIImageView+WebCache` 中的

- (void)sd_setImageWithURL:(NSURL *)url

placeholderImage:(UIImage *)placeholder;

这个方法为入口研究一下'SDWebImage'是如何工作的。

这个方法的实现:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

方法的唯一作用就是调用了另一个方法:

[self sd_setImageWithURL:placeholderImage:options:progress:completed:]  

这个方法也是`UIImageView+WebCache` 中的核心方法了。

操作的管理

这是这个方法的第一行代码:

[self sd_cancelCurrentImageLoad];

框架中的所有操作实际上都是通过一个 `operationDictionary` 来管理, 而这个字典实际上是动态的添加到 `UIView` 上的一个属性, 至于为什么添加到 `UIView` 上, 主要是因为这个  `operationDictionary` 需要在 `UIButton` 和 `UIImageView` 上重用, 所以需要添加到它们的根类上.

这行代码是要保证没有当前正在进行的异步下载操作, 不会与即将进行的操作发生冲突, 它会调用:

[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]

而这个方法会使当前 `UIImageView` 中的所有操作都被 `cancel`. 不会影响之后进行的下载操作.

占位图的实现

if (!(options & SDWebImageDelayPlaceholder)) {self.image = placeholder;
}

如果传入的 `options` 中没有 `SDWebImageDelayPlaceholder`(默认情况下 `options == 0`), 那么就会为 `UIImageView` 添加一个临时的 `image`, 也就是占位图.

获取图片

检测传入的 `url` 是否非空, 如果非空那么一个全局的 `SDWebImageManager` 就会调用以下的方法获取图片:

[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]

下载完成后会调用 `(SDWebImageCompletionWithFinishedBlock)completedBlock` 为 `UIImageView.image` 赋值, 添加上最终所需要的图片.

dispatch_main_sync_safe(^{if (!wself) return;if (image) {wself.image = image;[wself setNeedsLayout];} else {if ((options & SDWebImageDelayPlaceholder)) {wself.image = placeholder;[wself setNeedsLayout];}}if (completedBlock && finished) {completedBlock(image, error, cacheType, url);}
});

而最后, 在 `[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]` 返回 `operation` 的**同时**, 也会向 `operationDictionary` 中添加一个键值对, 来表示操作的正在进行:

[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

它将 `opertion` 存储到 `operationDictionary` 中方便以后的 `cancel`.到此为止我们已经对 `SDWebImage` 框架中的这一方法分析完了, 接下来我们将要分析 `SDWebImageManager` 中的方法

[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]

SDWebImageManager

这个类就是隐藏在 `UIImageView+WebCache` 背后, 用于处理异步下载和图片缓存的类, 当然你也可以直接使用 `SDWebImageManager` 的上述方法`downloadImageWithURL:options:progress:completed:` 来直接下载图片.可以看到, 这个类的主要作用就是为 `UIImageView+WebCache` 和 `SDWebImageDownloader, SDImageCache` 之间构建一个桥梁, 使它们能够更好的协同工作, 我们在这里分析这个核心方法的源代码, 它是如何协调异步下载和图片缓存的.

if ([url isKindOfClass:NSString.class]) {url = [NSURL URLWithString:(NSString *)url];
}if (![url isKindOfClass:NSURL.class]) {url = nil;
}

这块代码的功能是确定 `url` 是否被正确传入, 如果传入参数的是 `NSString` 类型就会被转换为 `NSURL`. 如果转换失败, 那么 `url` 会被赋值为空, 这个下载的操作就会出错.

既然我们获取了 `url`, 再通过 `url` 获取对应的 `key`

NSString *key = [self cacheKeyForURL:url];

下一步是使用 `key` 在缓存中查找以前是否下载过相同的图片.

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { ... }];

这里调用 `SDImageCache` 的实例方法 `queryDiskCacheForKey:done:` 来尝试在缓存中获取图片的数据. 而这个方法返回的就是货真价实的 `NSOperation`.

如果我们在缓存中查找到了对应的图片, 那么我们直接调用 `completedBlock` 回调块结束这一次的图片下载操作.

dispatch_main_sync_safe(^{completedBlock(image, nil, cacheType, YES, url);});

如果我们没有找到图片, 那么就会调用 `SDWebImageDownloader` 的实例方法:

id <SDWebImageOperation> subOperation =[self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { ... }];

如果这个方法返回了正确的 `downloadedImage`, 那么我们就会在全局的缓存中存储这个图片的数据:

[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];

并调用 `completedBlock` 对 `UIImageView` 或者 `UIButton` 添加图片, 或者进行其它的操作.

最后, 我们将这个 `subOperation` 的 `cancel` 操作添加到 `operation.cancelBlock` 中. 方便操作的取消.

operation.cancelBlock = ^{[subOperation cancel];}

SDWebImageCache

它维护了一个内存缓存和一个可选的磁盘缓存, 我们先来看一下在上一阶段中没有解读的两个方法, 首先是:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

这个方法的主要功能是异步的查询图片缓存. 因为图片的缓存可能在两个地方, 而该方法首先会在内存中查找是否有图片的缓存.

UIImage *image = [self imageFromMemoryCacheForKey:key];

这个 `imageFromMemoryCacheForKey` 方法会在 `SDWebImageCache` 维护的缓存 `memCache` 中查找是否有对应的数据, 而 `memCache` 就是一个 `NSCache`.

如果在内存中并没有找到图片的缓存的话, 就需要在磁盘中寻找了, 这个就比较麻烦了..

在这里会调用一个方法 `diskImageForKey` 这个方法的具体实现我在这里就不介绍了, 涉及到很多底层 `Core Foundation` 框架的知识, 不过这里文件名字的存储使用 `MD5` 处理过后的文件名.

CC_MD5(str, (CC_LONG)strlen(str), r);

对于其它的实现细节也就不多说了...

如果在磁盘中查找到对应的图片, 我们会将它复制到内存中, 以便下次的使用.

UIImage *diskImage = [self diskImageForKey:key];
if (diskImage) {CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale;[self.memCache setObject:diskImage forKey:key cost:cost];
}

这些就是 `SDImageCache` 的核心内容了, 而接下来将介绍如果缓存没有命中, 图片是如何被下载的.

SDWebImageDownloader

这个类的核心功能就是下载图片, 而核心方法就是上面提到的:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock; 

这个方法直接调用了另一个关键的方法:

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback

它为这个下载的操作添加回调的块, 在下载进行时, 或者在下载结束时执行一些操作, 先来阅读一下这个方法的源代码:

BOOL first = NO;if (!self.URLCallbacks[url]) {self.URLCallbacks[url] = [NSMutableArray new];first = YES;
}// Handle single download of simultaneous download request for the same URLNSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;if (first) {createCallback();
}

  

方法会先查看这个 `url` 是否有对应的 `callback`, 使用的是 `downloader` 持有的一个字典 `URLCallbacks`.

如果是第一次添加回调的话, 就会执行 `first = YES`, 这个赋值非常的关键, 因为 `first` 不为 `YES` 那么 HTTP 请求就不会被初始化, 图片也无法被获取.

然后, 在这个方法中会重新修正在 `URLCallbacks` 中存储的回调块.

NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;

如果是第一次添加回调块, 那么就会直接运行这个 `createCallback` 这个 block, 而这个 block, 就是我们在前一个方法 `downloadImageWithURL:options:progress:completed:` 中传入的回调块.

[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{ ... }];

我们下面来分析这个传入的无参数的代码. 首先这段代码初始化了一个 `NSMutableURLRequest`:

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:...timeoutInterval:timeoutInterval];

这个 `request` 就用于在之后发送 `HTTP` 请求.

在初始化了这个 `request` 之后, 又初始化了一个 `SDWebImageDownloaderOperation` 的实例, 这个实例, 就是用于请求网络资源的操作. 它是一个 `NSOperation` 的子类,

operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:requestoptions:optionsprogress:...completed:...cancelled:...}];

但是在初始化之后, 这个操作并不会开始(`NSOperation` 实例只有在调用 `start` 方法或者加入 `NSOperationQueue` 才会执行), 我们需要将这个操作加入到一个 `NSOperationQueue` 中.

[wself.downloadQueue addOperation:operation];

只有将它加入到这个下载队列中, 这个操作才会执行.

SDWebImageDownloaderOperation

这个类就是处理 HTTP 请求, URL 连接的类, 当这个类的实例被加入队列之后, `start` 方法就会被调用, 而 `start` 方法首先就会产生一个 `NSURLConnection`.

@synchronized (self) {if (self.isCancelled) {self.finished = YES;[self reset];return;}self.executing = YES;self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];self.thread = [NSThread currentThread];
}

而接下来这个 `connection` 就会开始运行:

[self.connection start];

它会发出一个 `SDWebImageDownloadStartNotification` 通知

[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];

代理

在 `start` 方法调用之后, 就是 `NSURLConnectionDataDelegate` 中代理方法的调用.

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection;

在这三个代理方法中的前两个会不停回调 `progressBlock` 来提示下载的进度.

而最后一个代理方法会在图片下载完成之后调用 `completionBlock` 来完成最后 `UIImageView.image` 的更新.

而这里调用的 `progressBlock` `completionBlock` `cancelBlock` 都是在之前存储在 `URLCallbacks` 字典中的.

这大概就是

[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

这个方法执行的全部过程了.

小结 

`SDWebImage` 的图片加载过程:

* 查看缓存

* 缓存命中

* 返回图片

* 更新 `UIImageView`

* 缓存未命中

* 异步下载图片

* 加入缓存

* 更新 `UIImageView`

  

  

  

转载于:https://www.cnblogs.com/neverMore-face/p/5439424.html

源码分析--SDWebImage相关推荐

  1. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  2. SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...

  3. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  4. SpringBoot-web开发(一): 静态资源的导入(源码分析)

    目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...

  5. Yolov3Yolov4网络结构与源码分析

    Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...

  6. ViewGroup的Touch事件分发(源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...

  7. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

  8. MyBatis原理分析之四:一次SQL查询的源码分析

    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: Java代码   String res ...

  9. [转]slf4j + log4j原理实现及源码分析

    slf4j + log4j原理实现及源码分析 转载于:https://www.cnblogs.com/jasonzeng888/p/6051080.html

  10. Spark源码分析之七:Task运行(一)

    在Task调度相关的两篇文章<Spark源码分析之五:Task调度(一)>与<Spark源码分析之六:Task调度(二)>中,我们大致了解了Task调度相关的主要逻辑,并且在T ...

最新文章

  1. 蔡明机器人对比_“百变”蔡明:一个拥有性感身材的“小品天后”!
  2. php mysql读写分离主从复制_mysql主从复制 读写分离原理及实现
  3. 生物信息之ME, HMM, MEMM, CRF
  4. 排序中减治法算法伪代码_算法浅谈——分治算法与归并、快速排序(附代码和动图演示)...
  5. Docverter – 文本文件轻松转换为 PDF,Docx 和 ePub 文件
  6. linux如何加载镜像,linux可以加载iso镜像文件到启动项吗
  7. 品‮方牌‬可以利用视‮号频‬做什么
  8. 7 win 卸载node_如何从windows中完全删除node.js_windows彻底卸载node教程
  9. Linux 下java jdk安装
  10. springboot中的controller注解没有生效
  11. SWF 学习笔记 ——《如何在内存中提取出加密的SWF》
  12. 软件测试工程师的日常工作流程
  13. 常见软件架构风格介绍
  14. c语言不满秩矩阵方程组的解,【线代】矩阵的秩与方程组的解[坑]
  15. vs 发生错误,需要终止调试... HRESULT=0x8000ffff。ErrorCode=0x0 解决办法
  16. Linux 一条命令删除某端口被占用的进程
  17. 【pd读取csv文件踩坑】读取csv文件时报错:UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xb5 in position 0
  18. 【IOS】IOS工程自动打包并发布脚本实现
  19. matlab清除所有变量,但是除了某些变量
  20. Android 设置壁纸被拉伸(固定壁纸 )

热门文章

  1. android 画布线条加粗,Android 对TextView添加删除线,下划线,加粗,斜体等效果
  2. php获取远程文件夹下的文件是否存在,PHP判断远程文件是否存在函数
  3. java笔记之过滤器
  4. MySQL解压缩版配置安装详解【图解】
  5. JSP的自定义标签(二)之带属性的标签
  6. 21秋期末考试工程项目管理10324k2
  7. 【渝粤题库】陕西师范大学500901 基础物理专题(力、热) 作业(专升本)
  8. sqli-lab(8)
  9. 获取本地ip地址 C#
  10. MySQL Study之--MySQL schema_information数据库