iOS中缓存无处不在,从底层的方法缓存到应用层的网络缓存等,缓存的存在,大大提高了应用的运行速度,提升了用户体验。这里讨论一下如何设计一个好的缓存框架。
缓存的数据结构
如果我们仅将缓存中的数据维护在一个链表中, 那么当我们需要从缓存中查找数据时, 就意味着我们需要遍历链表。 这样的设计, 其时间复杂度为O(n),是一种比较低效率的做法。而哈希表访问数据的时间复杂度为O(1), 另外缓存是以key-value形式保存,用哈希表再好不过了。
缓存淘汰策略
所谓缓存淘汰策略, 就是当缓存满了之后, 又有新数据需要加入内存时, 我们怎么从缓存中为新数据腾出空间的策略。这个权衡的过程就是所谓的缓存淘汰策略。
常见的淘汰算法:FIFO、LFU、LRU.
FIFO算法
FIFO(First in First out),先进先出。在FIFO Cache设计中,核心原则就是:如果一个数据最先进入缓存中,则应该最早淘汰掉。也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。
LFU算法
LFU(Least Frequently Used)最近最少使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。
注意LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。
LRU算法
缓存策略一般采用LRU算法。LRU, Least Recently Used的简写, 即近期最少使用算法。 该算法依据与程序的局部性原理, 其淘汰旧数据的策略是, 距离当前最久没有被访问过的数据应该被淘汰。
举个简单的例子:
假设缓存大小为3,数据访问序列为set(2,2),set(1,1),get(2),get(1),get(2),set(3,3),set(4,4),
则在set(4,4)时对于LFU算法应该淘汰(3,3),而LRU应该淘汰(1,1),FIFO应该淘汰(2,2)。
实现思路
用哈希表存储key-value键值对, 为了解决冲突,采用链地址法。为了实现LRU算法,还使用了双向链表将哈希表中的元素”串起来”,最新插入和最近访问的元素被插入或者移动到链表头部,这样当元素总量或者元素的总cost达到阈值时,从链表尾部开始删除元素(淘汰)。原理图如下:
二级缓存设计
缓存通常采用二级缓存设计。内存缓存是临时的、可以高速访问的,如果内存缓存没命中需要的数据,则需要在磁盘缓存中查找,若还未命中,则进行网络请求。
下面谈谈iOS自带的有缓存功能的类。
NSURLCache
NSURLCache专门用于对iOS中的网络请求进行缓存,使用起来十分简单。NSURLCache工作原理如下:
NSURLCache内部一个NSURLRequest对应一个NSCachedURLResponse,缓存的数据都保存到数据库中,对于大文件如视频、音频文件,以文件的形式另外保存。
NSURLCache的常见用法
(1)获得全局缓存对象
NSURLCache *cache = [NSURLCache sharedURLCache];
(2)设置内存缓存的最大容量(字节为单位,默认为512KB)
- (void)setMemoryCapacity:(NSUInteger)memoryCapacity;
(3)设置硬盘缓存的最大容量(字节为单位,默认为10M)
- (void)setDiskCapacity:(NSUInteger)diskCapacity;
(4)硬盘缓存的位置:
沙盒/Library/Caches
(5)取得某个请求的缓存
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
(6)清除某个请求的缓存
- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
(7)清除所有的缓存
- (void)removeAllCachedResponses;
缓存策略
iOS对NSURLRequest提供了7种缓存策略:
NSURLRequestUseProtocolCachePolicy // 默认的缓存策略(取决于协议)
NSURLRequestReloadIgnoringLocalCacheData // 忽略缓存,重新请求
NSURLRequestReloadIgnoringLocalAndRemoteCacheData // 未实现
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData // 忽略缓存,重新请求
NSURLRequestReturnCacheDataElseLoad// 有缓存就用缓存,没有缓存就重新请求
NSURLRequestReturnCacheDataDontLoad// 有缓存就用缓存,没有缓存就不发请求,当做请求出错处理(用于离线模式)
NSURLRequestReloadRevalidatingCacheData // 未实现
对于更新频率较高的资源不建议使用缓存,同时,缓存要定时清理以免占用太多磁盘空间。
使用示例
下面用NSURLCache来缓存MP4文件:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1. 设置 NSURLCache 2MB内存、100MB磁盘空间
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024 diskCapacity:100 * 1024 * 1024 diskPath:nil]; [NSURLCache setSharedURLCache:sharedCache];
// 2.创建请求
NSURL *url = [NSURL URLWithString:@"https://video.ubtrobot.com/jimu/post/180616131624932685.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
_request = request;
// 3.设置缓存策略(有缓存就用缓存,没有缓存就重新请求)
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
// 4.发送请求
NSURLSession *session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration];
_session = session;
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
NSLog(@"data.length === %lu", data.length);
}
}];
_dataTask = dataTask;
[dataTask resume];
return YES;
}
首次联网之后启动APP,视频文件会被缓存下来。使用一些代码获取缓存数据。
NSCachedURLResponse *response = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (response) {
NSLog(@"---这个请求已经存在缓存---");
if (response.data) {
NSLog(@"%@----%li", response.response.URL, response.data.length);
// 这里可以进行视频播放
}
} else {
NSLog(@"---这个请求没有缓存---");
}
注意: 启动的时候设置缓存为更大的容量十分重要,使用默认的你会发现无法缓存视频文件。
缓存下来的文件如下图所示:
更多NSURLCache内容参考:这里
NSCache
NSCache是专门用来处理内存缓存的类,使用方法跟NSMutableDictionary类似,但却又有很大的不同:
1. NSMutableDictionary会对key进行copy,value retain, NSCache不会对key进行copy,所以NSMutableDictionary的key要遵守NSCopy协议。
2. NSCache线程安全, NSMutableDictionary非线程安全。
3. NSCache有对象淘汰策略,通过在存值的时候指定cost,当总cost或者key-value个数达到limit时,会进行对象释放(非LRU算法)。这种淘汰策略在没收到内存警告是也会执行,从而降低内存峰值(minimizing its memory footprint)。
4. Retrieving something from an NSCache object returns an autoreleased result.
NSCache官方实现戳这里
YYCache
可以参考这篇解析YYCache实现原理的文章