如人饮水,冷暖自知

iOS 框架详解—「SDWebImage 框架结构」

引导


SDWebImage 是我们经常使用的一个异步图片加载库,在项目中使用SDWebImage来管理图片加载相关操作可以极大地提高开发效率,让我们更加专注于业务逻辑实现。
像这种经常用又比较重要的内容,我觉得要做到初步理解,然后梳理总结常用方法,到最后夯实基础、活学活用。为此,自己本着好好学习,了解权威的目的,决定解读 SDWebImage框架。

本篇文章主要从【SD 框架结构】学习总结,该模块学习将续更 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@白开水ln原著;

目录:

  1. SDWebImage的概论
  2. SDWebImage的内部结构
  3. SDWebImage的实现原理
  4. SDWebImage的工作流程
  5. SDWebImage框架基本使用
  6. SDWebImage框架内部细节
  7. SourceCodeToolsClassWechatPublic-Codeidea

1. SDWebImage的概论


  • 提供了一个UIImageView的category用来加载网络图片并且对网络图片的缓存进行管理
  • 采用异步方式来下载网络图片
  • 采用异步方式,使用memory+disk来缓存网络图片,自动管理缓存。
  • 支持GIF动画
  • 支持WebP格式
  • 同一个URL的网络图片不会被重复下载
  • 失效的URL不会被无限重试
  • 耗时操作都在子线程,确保不会阻塞主线程
  • 使用GCD和ARC
  • 支持Arm64

2. SDWebImage的内部结构


SDWebImage 内部结构.png

3. SDWebImage的实现原理


SDWebImage 是由一个 SDImageCache(一个处理缓存的类)SDWebImageDownloader(负责下载网络图片) ,而 SDWebImageManager则是管理者将前两者结合起来完成整个工作流程。

通过对UIImageView的类别扩展来实现异步加载替换图片的工作。主要用到的对象:

  • 1.UIImageView (WebCache)类别,入口封装,实现读取图片完成后的回调

  • 2.SDWebImageManager,对图片进行管理的中转站,记录那些图片正在读取。向下层读取Cache(调用SDImageCache),或者向网络读取对象(调用SDWebImageDownloader)。实现SDImageCacheSDWebImageDownloader的回调。

  • 3.SDImageCache,根据URL的MD5摘要对图片进行存储和读取(实现存在内存中或者存在硬盘上两种实现)实现图片和内存清理工作。

  • 4.SDWebImageDownloader,根据URL向网络读取数据(实现部分读取和全部读取后再通知回调两种方式

4. SDWebImage的工作流程


梳理SDWebImage的 工作流程.png

SDWebImage是一个成熟而且比较庞大的框架,但是在使用过程中并不需要太多的接口,这算是一种代码封装程度的体现。这里就介绍比较常用的几个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
梳理SDWebImage的 工作流程
1.入口方法:
调用 UIImageView+WebCache 的分类方法
self.imageView: sd_setImageWithURL: placeholderImage: options: progress: completed:
这个方法,如果传入的下载策略不是SDWebImageDelayPlaceholder 延迟显示占位图片,
默认情况下先显示 placeholderImage ,同时由SDWebImageManager根据 URL 来在本地查找图片。
2.进入方法:
初始化 SDWebImageManager 管理器并调用:
SDWebImageManager:downloadImageWithURL: options: progress: completed:
1). SDWebImageManager 是将UIImageView+WebCache同SDImageCache 链接起来的类,图片缓存是在内存缓存一份,在磁盘缓存一份.
3.SDImageCache:queryDiskCacheForKey: done:
该方法查找 cacheKey(URL对应的缓存KEY) 对应下载图片的缓存情况,先检查是否有内存缓存
4.检查该`cacheKey`对应的内存缓存,如果存在内存缓存,则直接返回,并把图片和存储方式(内存缓存)通过block块以参数的形式传递。前端来显示图片。
5.如果缓存对应的key为空,那么创建一个操作NSOperation 添加到串行队列,从磁盘查找图片是否已被下载缓存。
6.检查该 cacheKey 在硬盘缓存目录下尝试读取图片文件,这里回调doneBlock 结果,要在主线程中回调。
7.如果存在磁盘缓存,且应该把该图片保存一份到内存缓存中(则先计算该图片的cost(成本),如果空闲内存过小,会先清空内存缓存)。
然后并把图片和存储方式(磁盘缓存)通过block块以参数的形式传递,展示图片。
8.如果从硬盘缓存目录没有读取到图片,说明所有缓存都不存在该图片(无缓存),需要下载图片。
9.使用异步下载器下载图片:
初始化 SDWebImageDownloader 并创建新的图片下载任务,得到一的 SDWebImageOperation 对象。
SDWebImageDownloader:downloadImageWithURL: options: progress: completed:
图像下载完成或者出现错误时会通知代理
10.核心方法:下载图片的操作
把 SDWebImageOperation 对象添加到 NSOperationQueue 队列中开始异步下载,会调用 start 方法:
SDWebImageDownloaderOperation :在 start 方法中处理图片下载操作
创建 NSURLConnection链接对象发送请求,并设置代理

5. SDWebImage框架基本使用


UIImageView+WebCache.h 下载图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//下载图片的核心方法
/*
* 下载图片且需要获取下载进度(内存缓存&磁盘缓存)
* 根据图片的url下载图片并设置到ImageView上面去,异步下载并缓存
*
* @param url 图片的URL地址
* @param options 图片下载选项(策略),参考SDWebImageOptions的枚举值
* @param progressBlock 下载进度回调
* receivedSize 已经下载的数据大小
* expectedSize 要下载图片的总大小
* @param completedBlock 操作成功回调回调,该回调没有返回值
* Image:请求的 UIImage,如果出现错误,image参数是nil
* error:如果图片下载成功则error为nil,否则error有值
* @param cacheType:图片缓存类型(内存缓存|沙盒缓存|直接下载),SDImageCacheType枚举
* SDImageCacheTypeNone:从网络下载
* SDImageCacheTypeDisk:从本地缓存加载
* SDImageCacheTypeMemory:从内存缓存加载
* @param imageURL:图片的URL地址
*/
- (void)download1 {
[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.600_0.jpeg"] placeholderImage:[UIImage imageNamed:@"placeholder"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%f",1.0 * receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
self.imageView.image = image;
NSLog(@"download3--%@",[NSThread currentThread]);
switch (cacheType) {
case SDImageCacheTypeNone:
NSLog(@"网络下载");
break;
case SDImageCacheTypeDisk:
NSLog(@"使用磁盘缓存");
break;
case SDImageCacheTypeMemory:
NSLog(@"使用内存缓存");
break;
default:
break;
}
}];
}
SDWebImageManager 管理者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
* 只需要简单获得一张图片,不设置(内存缓存&磁盘缓存)
* 如果URL对应的图像在缓存中不存在,那么就下载指定的图片,否则返回缓存的图像
*
* @param finished:
* 1.如果图像下载完成则为YES
* 2.如果没有使用 SDWebImageDownloaderProgressiveDownload,最后一个参数一直是 YES
* 3.如果使用了 SDWebImageDownloaderProgressiveDownload 选项,此 block 会被重复调用
* 1)下载完成前,image 参数是部分图像,finished 参数是 NO
* 2)最后一次被调用时,image 参数是完整图像,而 finished 参数是 YES
* 3)如果出现错误,那么finished 参数也是 YES
*/
- (void)download2 {
[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.600_0.jpeg"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%f",1.0 * receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
self.imageView.image = image;
NSLog(@"download2--%@",[NSThread currentThread]);
switch (cacheType) {
case SDImageCacheTypeNone:
NSLog(@"网络下载");
break;
case SDImageCacheTypeDisk:
NSLog(@"使用磁盘缓存");
break;
case SDImageCacheTypeMemory:
NSLog(@"使用内存缓存");
break;
default:
break;
}
}];
}
SDWebImageDownloader 工具类(文件下载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 下载图片的核心方法
/*
* 不需要任何的缓存处理(没有做任何缓存处理)
* 使用给定的 URL 创建 SDWebImageDownloader 异步下载器实例
*/
- (void)download3 {
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.600_0.jpeg"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%f",1.0 * receivedSize / expectedSize);
} completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
/** 注意:completed完成后这里是在子线程中执行的 */
// 线程间通信(回到主线程刷新UI)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
NSLog(@"download3--%@",[NSThread currentThread]);
}];
}];
}

6. SDWebImage框架内部细节

1.SDImageCache是怎么做数据管理的?

SDImageCache分两个部分,一个是内存层面的,一个是磁盘层面的。
内存层面的相当是个缓存器,以Key-Value的形式存储图片。
当内存不够的时候会清除所有缓存图片。用搜索文件系统的方式做管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。
当SDWebImageManager向SDImageCache要资源时,先搜索内存层面的数据,如果有直接返回,
没有的话去访问磁盘,将图片从磁盘读取出来,将图片对象放到内存层面做备份,再返回调用层

2.系统级内存警告如何处理(面试)?
1
2
3
4
5
6
7
8
9
// 1.清空缓存
// clearDisk 清除磁盘缓存,直接删除然后重新创建(所有缓存目录中的文件全部删除,再创建一个同名空目录)
// cleanDisk 清除过期的磁盘缓存,计算当前缓存的大小,和设置的最大缓存数量比较,
如果超出那么会继续删除(按照文件的创建的先后顺序),直到小于最大缓存数量
过期是 maxCacheAge =7
[[SDWebImageManager sharedManager].imageCache cleanDisk];
// 2.取消当前所有的操作
[[SDWebImageManager sharedManager] cancelAll];
3.该框架内部对内存警告的处理方式?

内部通过监听通知的方式清理缓存

  • 1.监听到UIApplicationDidReceiveMemoryWarningNotification
    接收到内存警告的通知后,调用 clearMemory 方法,清除内存缓存

  • 2.当监听到UIApplicationWillTerminateNotification
    接收到应用程序将要终止通知,调用 cleanDisk 方法,清理过期(默认大于一周)的磁盘缓存

  • 3.当监听到UIApplicationDidEnterBackgroundNotification
    接收到应用程序进入后台通知,调用 backgroundCleanDisk 方法,清理过期磁盘缓存

补充
内存缓存: 使用NSCahce对象实现,最大内存缓存值以像素为单位
磁盘缓存: 使用 NSFileManager 存储在 Cache 目录中,最大磁盘缓存是以字节为单位,最大磁盘缓存的时间默认为1

4.最大并发数量
1
maxConcurrentOperationCount = 6
5.缓存文件的保存名称如何处理?

SDImageCache,拿到图片的URL路径,对该路径进行MD5加密对图片进行存储和读取。

6.该框架进行缓存处理的方式?

可变字典—>NSCache

7.如何判断图片的类型?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在判断图片类型的时候,只匹配第一个字节
#import "NSData+ImageContentType.h"// 判断图片类型
拿到文件的二进制数据,取出第一个字节和switch里的分支比较来判断
PNG:压缩比没有JPG高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
JPG:压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
GIF:序列桢动图,特点:只支持256种颜色!最流行的时候在19981999,有专利的!
SDWebImage缓存图片的名称是用URL的md5加密后生成的32位字符串作为文件名的
利用通知中心观察,能够保证缓存文件的大小始终在控制范围之内!
```
示例:
```objc
// 5.判断图片类型
NSData *imageData = [NSData dataWithContentsOfFile:@"/Users/sunhui/Desktop/Snip20170209_1.png"];
NSString *typeStr = [NSData sd_contentTypeForImageData:imageData];
NSLog(@"%@",typeStr);// image/png
8.播放Gif图片?
1
2
3
4
#import "UIImage+GIF.h"// 播放Gif图片
UIImage *image = [UIImage sd_animatedGIFNamed:@"imageGif"];
self.imageView.image = image;
9.队列中任务的处理方式?

FIFO,下载任务的执行方式:默认为先进先出

10.请求超时的时间?
1
downloadTimeout 默认:15

PS.
最后分享一下学习多线程自定义NSOperation下载图片思路:
自定义NSOperation下载图片思路.gif

文字解析:

  • 1.根据图片的url先去检查images(内存缓存)中该图片是否存在,如果存在就直接显示到cell上;否则去检查磁盘缓存(沙盒)。

  • 2.如果有磁盘缓存(沙盒),加载沙盒中对应的图片显示到cell上,再保存一份到内存缓存;否则先显示占位图片,再检查operations(操作缓存)中该图片是否正在下载,如果是 就等待下载;否则创建下载operations操作任务,保存到操作缓存中去下载。

  • 3.下载完成后(需要主动刷新显示(采用局部刷新)),将操作从操作缓存中移除,将图片在内存缓存(先) 和 沙盒(后)中各保存一份。

Reading


  • 如果在阅读过程中遇到 error || new ideas,希望你能 issue 我,我会及时补充谢谢。

  • 不管谁的博客上面写的 (也包括自己),阅读的你要敢于去验证;如人饮水,冷暖自知;(共勉)。

  • 喜欢可 赞赏 or Star一波;点击左上角关注 或 微众『Codeidea』,在 Demo 更新时收到邮件通知,便捷你的阅读。

🖋 Plain boiled water ln : 本文结束    感谢阅读 ^_^. Need coffee?
👁 At this time suggest 1 minute eye exercises

本文标题:iOS 框架详解—「SDWebImage 框架结构」

文章作者:白开水ln

原始链接:http://plainboiledwaterln.cn/SourceAnnotations/SDLibrary.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 『微众圈:Codeidea』本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和作者出处,谢谢合作!

「喜欢就留言or赞赏」but「支持不要超过你早餐费的 0.5」 ^_^.