简析
前段时间,和一个小伙交流,那小伙问我:
小伙:“NSString声明属性时,用什么修饰?”
我:“copy”
小伙:“为什么用copy,用strong有什么问题么?”
我:“如果使用strong修饰,只是对字符串做了浅拷贝,当某个对象持有这个属性时,会改变这个属性值。”
小伙:“那我就想让它改变呢?”
我:“……(⊙o⊙)?”
出来混也有三年了,竟然又在最基础的上面栽了,好尴尬。其实说到底还是自己内功修为不够。蜻蜓点水,对于开发者而言是大忌,做过几款APP就觉得自己怎样怎样,真真是井底之蛙🐸。
做为iOS开发者,相信大家都会或多或少的使用或了解过SDWebImage,剖析其源码的文章不在少数,今天我从问题驱动的角度来简单梳理下我所理解的SDWebImage。
SDWebImages图片类型识别问题
大家都知道,UIImageView默认情况下只能加载png类型的图片,加载jpg/gif等类型时是要单独处理的,那么SDWebImage是怎么识别网络图片的类型呢?
阅读源码,大家会发现在NSData的分类文件NSData+ImageContentType.m中,它是根据文件头来识别,即图片流文件的第一个字节判断。
|
|
OpenCV图片类型识别也类似,参见:include1224的博客:读文件头判断图片类型
SDWebImage的下载队列机制
SDWebImage加载网络图片的方式是异步加载的方式,不管是从性能方面还是从为用户节省流量的角度而言,SDWebImage做的都是比较好的。
那么,问题来了:
1、 异步加载多张图片时,SDWebImage是怎么处理的?是否有对应的并发队列?
2、如果有,它的并发队列运行机制是怎样的呢?既然是并发队列,最大的并发数是多少?
3、当多个图片下载任务结束时,在队列中移除的策略是怎样的,是先进先出?还是后进先出?
4、当某个图片的URL为错误链接,或者服务器异常,或者网络异常的情况下,SDWebImage有没有异常超时处理?如果有超时机制,时长是多少呢?
下面我将一一为大家找到答案:
SDWebImage网络图片下载是通过SDWebImageDownloader和SDWebImageDownloaderOperation类来完成的。
- SDWebImageDownloaderOperation封装了单个图片下载操作,它有一个start方法用来开启一个下载任务,看源码可以看到,在start方法体中有一段:12NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];sessionConfig.timeoutIntervalForRequest = 15;
在内部明确写了单个任务的超时时间15秒。
SDWebImageDownloader是用来管理SDWebImageDownloaderOperation图片下载任务的(另外在SDWebImageDownloader中也可以配置任务超时时长),它持有多个公有属性:maxConcurrentDownloads(最大并发数)、downloadTimeout(任务超时时长)、executionOrder(队列执行方式)等,维护着一个私有并发下载队列downloadQueue和一个最新任务添加任务lastAddedOperation。
看源码我们可以轻松了解到,SDWebImage的下载队列默认情况下是SDWebImageDownloaderFIFOExecutionOrder,是先进先出的,下载队列并发数为6。123downloadQueue.maxConcurrentOperationCount = 6;downloadTimeout: 15.0;executionOrder: SDWebImageDownloaderFIFOExecutionOrder;详见源码:
|
|
SDWebImage缓存机制
SDWebImage缓存机制其实由两部分组成:内存缓存、磁盘缓存。从SDImageCache文件中我们可以清楚地看出这一点,其中memCache即内存缓存,diskCachePath即磁盘缓存,数据文件存储在沙盒中:
内存缓存
先说下内存缓存memCache,为了完善内存缓存,SDWebImage实现了NSCache的一个子类AutoPurgeCache,扩充了NSCache,当内存警告时,它会接受UIApplicationDidReceiveMemoryWarningNotification通知,自动执行removeAllObjects操作。
|
|
如果大家细心的话会发现,SDWebImage做了内存缓存,当我们频繁的使用SDWebImage加载多张图片时,却为何基本不会出现内存暴涨的情况呢?其实这一切归功于自动释放池@autoreleasepool。
磁盘缓存
接下来咱们说下磁盘缓存,磁盘缓存文件是存储在沙盒中的,存储过程比较复杂。我先简单说下,SDWebImage加载图片的大致流程,相信从中,大家会对diskCache有所了解。
在使用SDWebImage时,往往是从UIImageView+WebCache文件开始的,我们使用SDWebImage第一步就是要引入UIImageView的分类WebCache,然后调用sd_setImageWithURL:方法,完成图片的异步加载。
图片加载的具体流程如下:
- 调用sd_setImageWithURL方法时,它首先是通过URL作为key查询内存缓存,即SDImageCache的memCache属性,如果存在直接显示到View上。
- 反之,将通过md5编码URL作为文件名,去沙盒(即SDImageCache的diskCachePath路径下)中查询有无此文件,如果存在,就把沙盒中的文件加载到内存缓存memCache中,然后通过SDWebImageDecoder解码后,直接显示到View上。
- 如果沙盒中不存在,则先将占位图片placeholderImage加载到View上,紧接着去SDWebImageDownloader的downloadQueue队列中,查找是否有正在下载该图片的下载任务,如果存在继续该任务。
- 如果下载队列不存在,创建图片下载任务SDWebImageDownloaderOperation,然后通过lastAddedOperation,根据对应的机制添加到下载并发队列downloadQueue中,下载完毕后,将操作在队列中移除,将图片添加到内存缓存中,直接显示到View,并将该文件压缩编码后存储到沙盒中,将通过md5编码URL作为文件名。
相信看到上面的流程后,大家对磁盘缓存机制有所了解,当然也带来了一些疑问,比如:
1、图片文件为什么使用md5编码的URL作为文件名?
2、磁盘缓存,既然称为缓存,就肯定有一定的时间期限,缓存的时长是多少?
3、文件过期之后,在什么时机清除过期图片文件的?
4、沙盒大小是有限度的,那么为SDWebImage预留的磁盘空间有没有大小限制?
5、如果我想清空所有的SDWebImage缓存怎么清除?如果我们需要清除特定的图片缓存又该怎么处理?
下文,我将为大家一一解答这一系列疑问:
SDWebImage缓存图片命名问题
SDWebImage是怎样维护缓存图片的?在SDImageCache文件中,我们不难发现,它是利用了MD5的压缩性特性、容易计算、强抗碰撞等特性,将图片的URL进行md5编码,作为文件名存储到沙盒中的。
MD5百度百科
MD5算法具有以下特点:
1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
|
|
SDWebImage缓存文件保留时长及缓存空间大小
既然是缓存,肯定有相应的时间期限,默认情况下SDWebImage的缓存时长为一周,并且缓存空间可以自定义。
过滤URL,禁用缓存
如果想过滤特定URL,不使用缓存机制,可以在对应位置加入如下代码过滤。
|
|
清除特定图片缓存
刚说过,SDWebImage加载图片是有缓存的,默认存储一周的时间。使用SDWebImage加载同样URL的图片时,优先会从缓存中取,而不是每次重新请求加载,那么问题来了,我们的头像/广告图等,需要实时刷新,我们要需要清除特定的图片缓存。
单单就头像/广告图更新问题而言,无非是更新缓存问题,有很多方法解决,
- 使用options:SDWebImageRefreshCached刷新缓存,但是有些童鞋反应该方法有闪烁问题,甚至有时并没有更新图片,所以保险起见,最好还是手动清缓存的方式。
- 每次清除掉图片缓存,重新加载的方式,代码如下:
|
|
清除过期文件的时机
通过上文的解答,大家知道磁盘缓存的文件是有时间期限的,那么,SDWebImage在什么时机清除过期文件的呢?在SDImageCache文件我们同样可以得到答案:
清除过期旧文件的时间点有两处:程序切到后台、杀死APP时。
|
|
具体源码如下:
本文已在版权印备案,如需转载请在版权印获取授权。
获取版权