ios收藏

SDWebImage原理

2019-06-20  本文已影响0人  __拼搏__

先放上github的总概念图的划分

上图是目前版本5.0.6的总览图的部分截图。

有上图可以看出,SD主要分为4个主要模块:

1、SDWebImageManager(管理类) 这个是SD的核心类,用于分配任务与管理任务。他一般不会直接参与任务的执行。而是把任务下发到下面两个功能类来做。充分体现了谁的任务谁做的解耦合思想。

2、SDImageCache(缓存类),这个类用于图片缓存。

3、SDImageLoader,这个类用于图片加载(主要是下载功能的核心SDWebImageDownloade(下载类))

4、UIkit的拓展。一般使用都是经过这些拓展来使用。方便直接使用SD的功能。

在通过UIkit的拓展类使用SD加载图片的时候,其实是通过SDWebImageManager类调用了缓存类与下载类经过一定的流程,才把图片显示到手机上。下面就图片显示简单的描述一下这个流程:

上图的标红已经把大致的流程显示的很清楚了。下面详细的描述一下图片的下载流程:

1.首先,无论是下载还是缓存,都分别代表一个任务。

2.SD在执行下载或者缓存的时候,都会把对应的任务封装到operation里如下图:

3.SD会把所有的operation封装到一个全局的字典里,这个字段是NSMa'pTable类

4.在所有操作之前,先判断这个View上有没有在执行任务,如果有就把即将开始的任务取消。没有就下一步

5.SD会调用管理类的loadImage方法。有缓存就加载缓存,没缓存就网络获取。(这一步下来再细聊)

6.当图片已经下载完毕或者从缓存中获取到之后,有两个选择:1.如果不需要设置图片就直接把图片返回给当前的View,并标记当前界面需要刷新。这样runloop在下一个周期,就会刷新view显示图片。2.如果需要设置这张图片(如加水印之类的处理)则把图片通过block返回给当前方法的调用者,并在调用者处理完成图片之后,再标记刷新。

下面聊聊SD的缓存原理:

SD的缓存由内存缓存和磁盘缓存同时控制。

1.先看看配置类:下图是压缩属性:

属性的注释已经很清楚了。这个属性负责图片的压缩。但压缩的过程会消耗一定的内存,如果图片过大,会造成内存爆针。这时候只要把这个属性置位不压缩就可以了。

下图是是否需要在内存中做缓存。

2.再看缓存类:

1.先了解一下内存缓存:

缓存类首先定义了一个内存缓存的对象。我们看一下这个对象:

这是一个继承于系统的NSCache的对象,并定义了一个NSMapTable的弱缓存表。

{NSMapTable与dic(语义:nscopy)有什么区别?NSMapTable拥有更多的内存语义,如copy,assign,strong。什么意思?

首先,当我们在用dic使用一个类(如NSString)作为key进行setValueForKey的时候,这个类就必须要实现NScopy协议。但是实现了这个协议后在setValueForKey时,dic会默认把这个类copy一下。这就会导致我们实际存储的东西跟想要存储的东西,不是同一个内存地址,也就是不是同一个东西。这不是我们想要的结果。

而NSMapTable拥有更多的内存语义。如weak,strong。可以参见SD对于这个weakCache的初始化

我们可以看到,这个NSMapTable把key做了强引用,把value做了弱引用。好处就是,因为不再有nscopy协议,保证这个了key,value一定就是我们传过来的key,value。而且还因为放在了全局的弱引用表中,当我们的对象(这个对象表示图片的内存)被释放之后,这个value也会就会被NSMapTable释放,而且由于value释放了,key也会被释放。这样既不用担心key不是我们想要存储的key,也不会担心key或者value无法释放。最主要的是,使用了NSMapTable后,我们就能手动管理要存储的图片内存的释放。}

那为什么系统有了用来处理内存缓存的NSCahe类,SD还要再继承这个类呢?

原因是因为NSCahe类的缓存释放时间完全由系统管理,我们无法得知NSCahe缓存的释放时间,这样可能在我们需要这个缓存的时候,系统却已经把这个缓存给释放了。所以SD继承于NSCahe后,又重新做了一个若缓存表,用来在需要某个缓存的时候,确定这个缓存不会被释放。

那这个机制具体是怎么实现的呢?

首先SDMemoryCache重写了NSCache的核心方法如:

既然重写,但是还是调用super方法。通过观察NSCache的声明文件不难发现。NSCache一定在实现的时候有个key与value对应的表没有暴露出来(这个表可能是字段也可能是数组,但是一定存在。因为如果不存在就不可能存的住keyvalue)。这就意味着系统的管理缓存释放,其实就是对这个表的管理。所以在给NSMapTable做setKeyvalue操作的时候,我们先把数据存一份在系统管理的表中,再存一份到我们自己管理的表中(就是刚才的weakCache)。但是这样做却多占用了一份内存。那究竟好处在哪,我们看一下取值方法:

取值方法步骤分为3步。第一步直接在系统的NSCache里查找,因为NSCache的释放完全由系统管理,所以取值的时候很可能value已经被系统释放了。第二步:如果已经被系统释放了,我们就先从我们自己建的表中取出这个value,这样保证了每次取value的时候,就算NSCache的那一份已经释放了,我们自己存的还能拿出来用。第三步:如果我们取出了value,首先要再调用一次NSCache的存储,把这个value存到NSCache中。保证了NSCache中尽可能的拥有这份value,这样在下次再取值的时候,如果NSCache中的value没有被释放,我们就能直接拿来用。

这就不难看出多占用一份内存的好处,我们是用内存空间换取了查询时间。保证了尽量高效查询的同时,又保证了数据一定不被释放。(这个思维方式真的屌)。

而且SD还监听了didReceiveMemoryWarning方法:

一旦出现内存警告,SD会立即释放已占用的内存。保证了就算SD存储了两份内存,当出现内存警告时,不会造成APP闪退。

我们再来看看磁盘缓存:

磁盘缓存,首先会创建一个缓存目录

然后把文件的key值进行MD5加密,再经过一些组合,最终得到文件名:

了解了内存缓存与磁盘缓存后,我们再来盘点一下,SD究竟怎么把这两种缓存结合到一起的。

首先执行缓存操作的,一定是我们的管理类。执行方法就是loadImageWithURL。然后我们找到该方法中的查询语句,点进去,看实现

实现如下,我把核心的东西,标红了

图中5个点比较重要。再讲解5个点之前,我们应该知道无论是存磁盘还是存硬盘,存的都是二进制数据。

其中标点1:是从缓存中查找图片。

标点2:因为从磁盘中查找比较耗时,为了确定操作安全,新建一个opration对象,并把加入全局的字段中,如果发生一个空间在图片未加载完全时就又加载了一次这张图片,则直接需要再次加载的操作。这就跟文章开头提的对应上了。

标点3:自动释放池,当一个快代码会产生大量临时变量时,为了能让这些临时变量用完立马就释放,就需要给这些变量加入自动释放池中。又因为整个APP代码就是在一个自动释放池中,这一块就算不加这个释放池也没关系。但是释放的时机会慢不少,无法最快速度的回收内存。

标点4:根据key找出2进制数据。

标点5:拿到数据后,再存一份到内存中。这样下次再查的时候,就能最快的查找到。

上一篇下一篇

猜你喜欢

热点阅读