胡说八道 - 3 SDWebImage 见语

SDWebImage 简要介绍

SDWebImage 是一款性能卓越、流行度高的网络图片下载框架。对 SDWebImage 框架代码解读的文章数不胜数,这里只胡说八道框架的设计思想。
[注]:推荐南峰子的博客 《源码解析:SDWebImage 实现分析》
《计算机组成与设计:硬件/软件接口》 CPU 和 存储管理讲的很好。第三版、第四版均可。

框架设计思想剖析

SDWebImage 加载图片的流程

流程参考这里 《那些著名或非著名的 iOS 面试题 - 前编》

1.入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。

2.进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.

3.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

4.SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。

5.如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。

7.如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

8.如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。

9.共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

10.图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

11.connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。

12.connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

14.在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。

15.imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

16.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

总结

总体设计思路

  1. 内存中维护一个图片缓存作为一级缓存,辅存中维护一个图片缓存作为二级缓存。同时共享的下载器缓存下载任务,减少下载时的资源消耗。进行文件查找、文件下载、图片解码、文件读写操作等都使用后台线程完成,减少对程序性能的影响。

设计经验

  1. 设计模式
    Observer、Delegate 、Singleton 的混合使用,使各功能组件的配合更加高效。这些简单的设计模式也没有那么不堪,关键是不乱套、不滥用。
  2. 性能优化
    1. 功能明确分开
      文件查找、文件下载、文件读写、图片解码等操作都交由对应的功能组件完成,代码清晰,结构明确。
  3. 多线程的使用
    处理必要的 UI 数据交换,其他的操作都尽可能的在后台线程完成,减少对主线程的影响。
  4. 架构设计
  5. 多级缓存的灵活使用
    内存中缓存图片作为一级缓存,辅存中缓存图片作为二级缓存;还对可能出现资源损耗的下载操作实现一次缓存。
    [注] 缓存的使用不是越多多好,请慎重选择使用。
  6. 功能组件的设置
    将功能组件分开,各司其职。做到代码结构清晰,功能有条不紊。(这里将‘文件下载’、‘文件IO’的操作称为功能组件并不合适,只是为了便于理解思路。)

题外话

  1. 多线程
    什么时候应该使用多线程?
  2. 完成线程或者任务间的同步操作
  3. 减少多主线程的性能影响,提高反应速度。使用后台线程。

多线程中应该考虑什么?

  1. 添加什么方式的任务到队列中?同步方式还是异步方式?(同步任务和异步任务的区别是什么?为是否阻塞当前线程,以同步方式添加任务到队列中会阻塞当前线程)

  2. 怎么去执行线程?串行还是并行?

3. 线程安全的?
典型的解决线程安全问题的方法:使用线程锁实现。对应各有优缺点。
这里推荐解决线程安全问题几篇文章:1.Objc 线程安全类的设计系列文章 2.多线程开发之线程安全 3.上的 iOS 多线程开发-线程安全

[注]1. 一个典型的 GCD 死锁问题
// 1 打印当前线程号
NSLog("之前 - %@", NSThread.currentThread())
// 2 在当前线程中,向主队列中添加一个同步任务
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
NSLog("sync - %@", NSThread.currentThread())
})
// 3 没有切换线程,在主线程中打印
NSLog("之后 - %@", NSThread.currentThread())
结果:只能输出:之前 - XX
步骤分解:
1 在主线程中先获取主线程队列,以同步的方式添加一个任务到主线程队列中。
2 主线程队列中的任务会被取出串行执行,同时因为是同步方式,所以会阻塞当前主队列线程
3 主线程被阻塞,不能被执行
[注] 2. 实现线程间数据同步或者通信
两种实现方式:GCD 实现 和 NSOperation 实现。

  1. 缓存
    强烈推荐阅读《计算机组成与设计:硬件与软件接口》
  2. 架构
    好的架构是一步一步进化出来的,而不是一次就完成所有架构的。但是不可否认,编写代码时,应该遵循良好的编码规范和设计方法

小技巧

  1. 不同网络状态下的图片加载处理
    场景:3/4G 和 WiFi 环境下的高清图片和普通质量图片的显示问题
    SDWebImage 会默认加载缓存中的高清图片,为了节省用户的流量,应该对网络状态进行判断,控制加载不同质量的图片。但是这还没有完,有些用户默认设置在 3/4 G 网络环境下加载普通质量的图片(设置在偏好设置中),该怎么进行省流处理?
    例子:《iOS 开发-你真的会用 SDWebImage?》
  2. 下载进度展示
    场景:正在下载的 GIF 图片,被点击浏览大图,怎么保证小图和大图中显示的下载进度是一致的?
  3. SDWebImage 设置图片圆角,避免离屏渲染
    场景:设置图片圆角,既要快,又不能有离屏渲染。
    key: 在 ImageView 的分类中,开启后台线程使用 Q2D 进行图片绘制。

任务

  1. 自己实现一个文件下载器
    例子:《高效图片轮播 - 两个 UIImageView 实现》
  2. 断点续传
    key:使用 HTTP 中的 HEAD 方法,设置每次下载的文件长度。
    其他的方法请评论告知我,谢谢。

你可能感兴趣的:(胡说八道 - 3 SDWebImage 见语)