缓存

        缓存在计算机中有很多定义,但作用却很相似,目的是为了方便更快地获取数据,是数据交换的缓冲区。

        在硬件方面,缓存(也可以理解为CPU缓存)一般相当于硬盘的一种更快的数据交换方式,同时与内存(RAM)也有所不同。访问RAM中的数据比硬盘要快很多,而对硬件缓存的访问要比RAM还要快。一般来说,缓存是当作RAM和CPU交换的中间人,将CPU所需的部分数据备份在缓存中,方便以后更快地使用这些数据。不仅如此,RAM、硬盘、显卡自身都有自己的缓存,作用和CPU缓存类似。

        我们使用缓存的原因都有一个共同点,就是获取数据的方式过于昂贵,或者速度过慢,对于移动端来说,这个代价就是网络请求很慢,并且会消耗流量和电量。虽然现在人们普遍使用了4G,或者公司的后端优化的足够好,一个请求的时间控制在几十毫秒或几百毫秒,但这相对于读取缓存来说,仍然是太慢太慢。并且请求是需要花费时间和流量的,随谈对于一个请求来说,并不能花费多少和电量,但从长远来看,则会积少成多,避免不必要的浪费,是一个高性能APP的体现。

         在编程语言中,对于缓存其实没有一个特别清楚的定义,缓存的形式多种多样,例如服务端编程语言中,将从数据库获取到的数据存放到Redis一份,称为Redis缓存;而对于客户端语言来说,缓存仍然存在很多含义。例如,对于iOS开发来说,缓存可以普遍理解为对网络请求获取到的数据进行存储或者持有,也就是说,缓存除了包括数据库、归档、NSUserDefaults等存储方法外,还包括内存缓存,更甚者,我们创建了一个本地临时变量也都可以理解为缓存。(注意,这里说到的缓存都是相对于网络请求的,对于数据库来说,还可以创建一份相对于数据库的内存缓存,避免重复对数据库的访问。)

        实际上,可能很少自己去写一些关于缓存方面的东西,但使用的第三方库有很多会使用到缓存机制。例如,知名的图片请求缓存框架SDWebImage,就使用了非常完善的图片缓存机制。下面简单介绍一下SDWebImage的工作原理,当我们需要显示图片时,会先显示默认的占位图,然后从内存中查找,查找到了就直接返回该图片;如果内存中不存在,则会异步从硬盘中读取,如果在磁盘中存在,则返回该图片并同时将其缓存到内存中;如果磁盘中也不存在,就会异步下载该图片到磁盘路径,并缓存到内存中,同时也返回该图片。这是SDWebImage对于缓存的基本原理,当然代码海涉及到缓存下载、个数限制、过期时间、缓存清理等操作,这些都是配套的缓存机制,不过这里不做细究,主要的关注点是其内存缓存和磁盘缓存。

        对于磁盘缓存的策略主要是以文件的形式进行存储,SDWebImage的磁盘缓存就是将一个个图片以文件的形式存储在沙盒目录中,同时文件名是以图片的URL进行MD5加密获得。

        而对于内存缓存,主要有几种方式。其一是设置一个全局字典或链表,手动进行增、删、改、查操作;另外一个是系统提供的专门用于内存缓存的类NSCache。NSCache可以缓存任意对象,并能够设置最大缓存个数,在超出个数的情况下会释放最长时间没有被访问到的那个。例如,我们设置缓存的countLimit个数限制为5,然后依次存入字符串:“1”、“2”、“3”、“4”、“5”,如果这个时候我们再增加一个“6”,则会删除“1”,但如果在增加“6”之前,访问过一次“1”,则增加“6”会导致“2”被删除。

    

      NSCache有一个协议NSCacheDelegate,这个协议只有一个方法cache:(NSCache *)cache willEvictObject:(id)obj,该方法是当添加的缓存个数超出限制时可能会触发的。为什么说“可能会”?因为这个缓存的个数限制并不是严格的。在上面的示例代码中,设置了NSCache的缓存个数为5个,并一次性在其中存放了6个缓存数据,这些数据都是键值相同的。打印结果如下:

         可以看到,如果需要存放第6个缓存数据,需要先删除一个,是否一定从头开始呢?我们将上面代码的添加缓存数据进行修改:

          我们模拟了这样一种场景,在NSCache中缓存个数已满的情况下,先访问key为@1的缓存数据。表示这个数据最近被使用过,再添加@6,这样会导致什么情况呢?

        可以发现与之前的打印结果不同,这次删除的不是第一个元素,而是删除了第二个元素,因此这就是我们一开始所说的,会删除最长时间没有被访问到的那个缓存元素。

        那么对于NSCache来说,这是系统为我们提供的一个临时存储缓存的类,可以看作一个存放键值对的几个,有些类似于字典,不过NSCache为我们提供了添加、一出数据和缓存一出删除(驱逐)的机制,尤其是这个溢出删除的机制,保证了我们的缓存不会存放太多的数据,从而不会出现为了提升性能使用缓存而造成内存使用过多的情况。

       说到NSCache,就不得不提到与之相关的一个协议NSDiscardableContent,这是一个在NSObject.h中的协议,实现该协议的对象会采取一种类似于ARC引用计数的机制,存在一个计数的概念,如果该对象正在被读取或者仍然被需要,则这个计数会一直大于0,否则为0,表示在内存资源紧张时会被释放。我们先看一下NSDiscardableContent协议的几个方法:

        

       第一个方法在我们要使用某个缓存数据之前使用,会使访问计数加1,不会被缓存释放;当访问结束调用第二个方法时会使访问计数减1;第三个方法在NSCache个数超出最大情况下清除某个时,或者NSCache设置evictsObjectsWithDiscardedContent为YES时调用,可以在此时做一些资源清理的工作;第四个方法是手动判断当前缓存是否被废弃,例如缓存的时间已经过久,不再具有使用价值。其中前两个协议方法是组合使用的,防止在缓存数据的访问中被释放,后两个方法是判断缓存数据是否可用,以及被废弃前的处理操作。需要注意的是,缓存被废弃不一定会被销毁,所以在使用第一个方法时应当判断当前缓存内容是否有效。

        例如上面的示例代码,MyData类实现NSDiscardableContent协议,那么从缓存中取键为“data2”的数据对象时,如下。

        MyData *data22 = [self.cache objectForKey:@"data2"];

       如果MyData实现的-(BOOL)isContentDiscarded方法返回YES,则data22不为空,相反,如果为NO的话data22则为nil。

       举一个缓存的使用场景,还是经常谈到的UITableView的cell高度问题,前面有内容提到过,关于不定高度cell的处理方式有很多,使用AutoLayout动态行高虽然方便,但仍然不比用Frame计算,但由于UITableView自身机制的原因,对于复杂的cell在滑动效果上,采用计算好的高度值远比用AutoLayout要明显好得多。很多情况下,对于手动算高得到的高度,需要将起缓存起来,进一步提高效率。而在缓存策略上,使用NSCache就会非常适合,首先可以设置缓存的个数,防止缓存过多的数据,其次还可以根据内容选择性缓存,以及处理缓存废弃。

        假设我们正在做一款聊天应用,在聊天页面,每一条消息都是一个cell,这个页面的消息可能会有很多,成百上千,并且消息内容都不尽一致,我们需要做很多的操作来保障这个页面尽可能的优化。假设要做的有三点:1.根据消息内容计算高度并缓存;2.如果页面的消息个数最多为500条,其后每再增加100条,将前100条消息释放;3.设置缓存有效期为10分钟。

       首先设计消息类:

      

       暂且忽略消息的其他字段,只需要关注这个isExpired字段,其返回值实现是根据消息时间戳判断是否在10分钟内。

      为了更好地表示cell的高度,我们专门创建了一个类,并且实现了NSDiscardableContent协议。

     

       该类中,有一个弱持有的message,是为了不持有消息对象,当消息对象个数达到上限时,在其手动释放前一百个消息后,使这里的message属性置为nil。并重写了message属性的setter方法,在setter时,根据消息内容,计算出高度,并赋值给height属性。在实现的NSDiscardableContent协议方法中,先看第四个方法- (BOOL)isContentDiscarded,缓存被取出时会被系统调用,在这里如果其弱引用的message被释放了,则其内容自然是毫无意义的,应当被丢弃,如果返回YES,再根据第一个方法判断这个消息是否过期,相当于第四个方法是从客观角度判断消息是否可用,而第一个方法是从主观判断其是否可用。

        至此,对于NSDiscardableContent协议,在本例中有了很好的使用。具体在控制器中的所有代码如下。

     

          

      以上介绍了NSCache和NSDiscardableContent协议,以及对它们的使用。其实对于缓存来说,如何缓存,以及如何废弃,这是一个很值得研究的话题,缓存存在很多算法, LFU(Least Frequently Used)、LRU(Least Recently User)、2Q(Two Queues)都是比较知名的缓存算法,至于使用哪一种缓存算法,没有最好的,只有最合适的,要根据实际开发中的应用需求灵活运用。      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值