这是笔者学习 SDWebImage 源码时的笔记,对它有着很深的怨念呢。😊
功能
SDWebImage 提供的主要功能如下:
- 提供异步图片(后台)下载并保证不阻塞主线程;
- 支持内存和磁盘双缓存策略,保证同一 url 不会请求多次;
- 支持 WebP/Gif 等格式;
结构
SDWebImageManager
协调 SDImageCache
和 SDWebImageDownloader
这两个模块的工作,内部使用自定义的 SDWebImageCombinedOperation
直接或间接引用对图片的缓存查询以及下载的操作,便于取消。
原理
对于每一次的图片下载操作,imageManager 让 cacheManager 去缓存中查找,由于在内存缓存中查找的速度很快,所以只在对磁盘缓存查询时,才异步在 ioQueue 中执行查询任务,避免阻塞主线程。查询完成之后,imageDownloader 开始工作,如果 “所需缓存不存在/缓存过期/ SDWebImageRefreshCached 开启”,downloader 就会产生一个 loaderOperation(继承于 NSOperation
)放入 downloadQueue,等待启动。一旦操作启动, urlConnection 去下载图片数据。这个操作是在子线程去执行的,为了能保证接收到 urlConnection 回调回来的数据,不能让这个线程把 -start
内的的代码执行完,就结束自己的生命,所以需要 runLoop 去驱动线程接收数据,直到 “数据接收完成/发生错误/主动取消操作” 才停止这个runLoop,接收完成了之后发送通知并执行预设的 block。
性能
SDWebImage 的源码中有两个地方提到 ‘performance’ 这个词:
-
在
shouldDecompressImages
这个 flag 中的注释,提到了如果开启了这个 flag 可以提高性能,换来的代价是消耗更多的内存。要弄懂shouldDecompressImages
的意义必须先了解图片从读取到显示的过程1. 从磁盘拷贝数据到内核缓冲区 2. 从内核缓冲区复制数据到用户空间 3. 生成 UIImageView,把图像数据赋值给 UIImageView 4. 如果图像数据为未解码的 PNG/JPG,解码为位图数据 5. CATransaction 捕获到 UIImageView layer 树的变化 6. 主线程 Runloop 提交 CATransaction,开始进行图像渲染 6.1 如果数据没有字节对齐,Core Animation 会再拷贝一份数据,进行字节对齐 6.2 GPU处理位图数据,进行渲染
在这个过程中我们可以看到,如果我们直接将未经解码的图像数据传递给 UIImageView 对象,那么该对象就会在主线程中去解码图像数据,在图片特别大的情况下耗时相应的也会特别多,因此 SDWebImage 在解码图片这一步上做了优化,放到了子线程中执行,这样拖动 tableView 的时候不至于阻塞主线程。
- 在
SDWebImageRefreshCached
这个 flag 中的注释提到,使用NSURLCache
而非SDWebImageCache
会降低一点性能。因为对于每个 HTTP 请求,我们都可以对NSURLCache
(如果使用)设置相应的缓存策略。通常在响应返回之后,客户端缓存这一份数据,通过响应头的 Expires, Cache-Control 等字段决定这份缓存什么时候过期。如果缓存过期,HTTP 允许缓存端发送条件 GET 请求到服务器,询问这份缓存的内容是否仍然是“新鲜的”。如果服务器对原来客户端缓存过的数据进行了更改,那我们就有必要从服务器中再取一份数据。综上,如果某个 url 对应的资源时常发生变动,那么我们应该启用NSURLCache
,而图片通常是静态的,所以启用SDWebImageCache
可以跳过询问缓存是否新鲜这个步骤,提高一点查询速度。
总结
有更好的网络图片加载库吗?当然,据闻 FastImageCache 和 YYWebImage 做的优化似乎更多。
然而现在只能等有空的时候再膜一番了。