HTTP缓存
Web缓存是可以自动保存常见文档副本的HTTP设备。当Web请求到达缓存时,如果本地有“已经缓存”的副本,就可以从本地存储设备,而不是原始服务器中获取这个文档。具有如下优点:
- 减少了冗余的数据传输
- 缓解了网络瓶颈问题(瞬间拥塞,Flash Crowds)
- 降低了对原始服务器的要求
- 降低了距离时延
一些概念
- 缓存命中(cache hit):可用所请求资源的本地副本为到来的请求提供服务
缓存未命中(cache miss):到达的请求由于没有所请求资源的可用本地副本而被转发给原始服务器。
再验证(revalidation):HTTP设备对其缓存在本地的资源副本进行“新鲜度检测”(检测是否仍与原始服务器上的资源一致)
缓存分类以及拓扑结构
缓存包括私有缓存和公有缓存。
公有缓存是特殊的共享代理服务器,被称为缓存代理服务器(caching proxy server)或代理缓存(proxy cache)
公有缓存接受来自多个用户的访问,缓存用户群体中不同的兴趣点,能更好的减少冗余流量
缓存的拓扑结构包括层次结构(herarchy)和网状结构(cache mesh)。更常用的是层次结构,如下图所示。

已缓存的资源副本的使用策略

- 当一个GET Request到来时,首先查询设备上是否已存储对应的资源,如果没有,则通过网络向原始服务器请求资源
- 对于从网络中获取到的资源,iOS设备需要决定是否在本地存储该资源(本文下一部分内容)
- 如果本地有缓存,查看如果Request是否要求进行再验证
- 如果Request要求再验证,则通过HEAD方式进行再验证
- 如果Request不要求再验证,当本地存储的Resonse足够新鲜时,直接返回缓存的资源,否则通过HEAD请求进行再验证
- 对于HEAD请求,如果服务器上的该资源被更改了,服务器返回的响应实体中包含有新鲜的资源内容,否则返回Body为空的response.
缓存控制
服务器可通过在响应中添加缓存控制首部,以控制发生在文档对象上的缓存行为。
指令 | 目的 |
---|---|
Cache-Control:max-stale 或Cache-Control:max-stale = <s> |
可以提供随意过期的资源 |
Cache-Control:min-fresh = <s> |
至少在未来s秒内资源要保持新鲜 |
Cache-Control:max-age = <s> |
不能使用缓存时间长于s秒的资源 |
Cache-Control:no-cache 或Pragma:no-cache |
除非资源进行了再验证,否则这个客户端不会接受已缓存的资源 |
Cache-Control:no-store |
缓存应该尽快从存储器中删除资源的痕迹 |
Cache-Control:only-if-cached |
只有当缓存中有副本存在时,客户端才会获取一份副本 |
iOS URL Loading System 中的Cache
“缓存”一词来自英语单词Cache,有意思的是,实际上这个词有两个意思:
1. 藏物处(名词)
2. 贮藏(动词)
客户端(不管是电脑上的浏览器,还是移动设备)一般自带缓存,可以缓存一些常用文档。
此部分主要关注iOS URL Loading System中的Cache,围绕两个方面展开:
1. iOS设备作为一个带有缓存功能的HTTP设备,对到来的Request的处理(是否使用本地资源副本来满足用户发起的请求)
2. iOS对于从网络中获取的Response中的资源的存储控制
对Request的处理
iOS URL Loading System通过枚举NSURLRequestCachePolicy提供了不同的缓存使用策略,让用户更好的控制系统对于网络请求的处理。
NSURLRequestCachePolicy
NSURLRequestCachePolicy指明当一个请求到来时,应该如何使用本地缓存的资源副本,默认值为NSURLRequestUseProtocolCachePolicy。
- NSURLRequestUseProtocolCachePolicy
- 使用和figure1一样的判决逻辑。
- NSURLRequestReloadIgnoringLocalCacheData
- 资源应该从网络中获取原始资源,不允许使用本地缓存的数据
- 如果是byte-range请求,必须使用此策略
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData
- 资源应该从原始服务器上获取,不允许使用任何一种缓存的资源(本地缓存/代理缓存/网络中的缓存设备上的缓存)
- 开发者文档指出:此值还未实现,也不应该被使用
- NSURLRequestReturnCacheDataElseLoad
- 如果本地没有对应的资源副本,使用该副本(忽略其新鲜度)
- 如果本地没有对应的资源副本,则通过网络请求原始资源。
- NSURLRequestReturnCacheDataDontLoad
- 如果本地没有对应的资源副本,使用该副本(忽略其新鲜度)
- 如果本地没有对应的资源副本,不会尝试从网络中下载原始资源。(类似于离线状态)。
- NSURLRequestReloadRevalidatingCacheData
- 如果本地缓存有资源副本,对其进行再验证,如果通过,该资源将被返回给App。
- 否则从网络中下载原始资源。
- 此值未实现
URL Loading System中可在两个地方设置NSURLRequestCachePolicy:MutableRequest对象的cachePolicy属性,NSURLSessionConfiguration对象的requestCachePolicy属性
缓存资源
对于从网络上获取的资源,可在本地进行缓存(在移动设备上生成该资源的一个副本),这样下一次再请求同样的资源时,可直接返回本地存储的资源副本。
NSURLCacheStoragePolicy
NSURLCacheStoragePolicy指明一个NSCachedURLResponse对象的缓存策略。
- NSURLCacheStorageAllowed:不加限制地允许存储
- NSURLCacheStorageAllowedInMemoryOnly:只允许存储在内存中,不允许存储在磁盘上
- NSURLCacheStorageNotAllowed:不允许存储
NSCachedURLResponse
URL Loading System在NSCachedURLResponse封装了原始的NSURLResponse以及对应的数据资源,通过该对象来实现对HTTP响应的资源的缓存控制。
NSCachedURLResponse包含如下属性:
@property(readonly, copy) NSData *data;//HTTP响应实体中的数据资源
@property(readonly, copy) NSURLResponse *response;//原始响应
@property(readonly) NSURLCacheStoragePolicy storagePolicy;//存储控制策略
@property(readonly, copy) NSDictionary *userInfo;//用户需要额外存储的信息
URLSession:dataTask:willCacheResponse:completionHandler:
此方法为NSURLSessionDataDelegate协议的实例方法,在某个task对应的URLSession:dataTask:didReceiveData:
最后一次被调用(也就是获取完某个Response中所有的data)后被调用。
函数原型
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler
应该在此方法中执行Handler,传入的NSCachedURLResponse表明了代理对于缓存的存储控制
- 若传入NULL,表明不允许缓存响应
- 若传入proposedResponse,表示缓存原始响应
- 如果入修改后的NSCachedURLResponse对象,则表示存储该对象(可在其userInfo中添加需要额外存储的信息)
这个方法只有允许缓存的条件下才会被调用,以确定用户最后的存储控制策略,具体包括
- The provided response came from the server, rather than out of the cache
- The session configuration’s cache policy allows caching.
- The provided NSURLRequest object's cache policy(if applicable) allows caching.
- The cache-related headers in the server’s response (if present) allow caching.
- response尺寸足够小(比如当缓存在磁盘上时,所缓存的内容不能大于磁盘大小的5%)
实践分析
缓存存储的路径
对于每个iOS APP.在沙盒中的Library下都会有一个Caches文件夹,存储App需要缓存的数据

一旦有相关的网络请求后,Caches下会增加一个路径,缓存网络请求的相关数据(如果需要缓存的话)

接下来的讲解中,我们会通过查看Caches/com.iclound.URL-Section中的内容来验证URL Loading System中的Cache Control。
均通过为session设置delegate实现相关的网络请求。
测试
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self.dele delegateQueue:nil];
NSURL *url = [NSURL URLWithString:@"http://115.159.219.141:8000/promotion/list/"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.cachePolicy = NSURLRequestReturnCacheDataDontLoad;
[[session dataTaskWithRequest:request] resume];
在Caches/com.iclound.URL-Section为空(未缓存任何数据)时将 request.cachePolicy设置为NSURLRequestReturnCacheDataDontLoad,可以发现
- 程序直接调用了
URLSession:task:didCompleteWithError:
方法,结束了task(因为本地没有对应的资源副本,系统也不会从网络中获取资源) URLSession:dataTask:didReceiveData:
和URLSession:dataTask:willCacheResponse:completionHandler:
均未被调用
测试1
request.cachePolicy设置为NSURLRequestUseProtocolCachePolicy。运行程序。URLSession:dataTask:willCacheResponse:completionHandler:
函数添加如下语句
completionHandler(NULL);
运行程序,发现能正常获取数据,但是Caches/com.iclound.URL-Section中未增加任何内容(说明从网络中获取的资源没有被缓存)
测试2
URLSession:dataTask:willCacheResponse:completionHandler:
函数改为如下实现
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler {
NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response data:proposedResponse.data userInfo:nil storagePolicy:NSURLCacheStorageNotAllowed];
completionHandler(newCachedResponse);
}
运行结果:可以去得到数据,但是数据不会被缓存。
测试3
URLSession:dataTask:willCacheResponse:completionHandler:
函数中
completionHandler(newCachedResponse);
改为completionHandler(proposedResponse);
运行发现:
URLSession:dataTask:didReceiveData:
被正常调用,从服务器中取回了数据Caches/com.iclound.URL-Section中添加了新的内容,如下图所示

用vim打开此文件,可以看到文件中的内容正式代理方法中取得的内容。说明URL Loading System从网络中获取了对应的资源并缓存在本地了。测试4
request.cachePolicy设置为NSURLRequestReturnCacheDataDontLoad。
原本多次调用
URLSession:dataTask:didReceiveData:
才能从网络中获取完整的资源,本次测试中此函数只被调用了一次(大概是从本地取得的数据很好取,吧O_O)URLSession:dataTask:willCacheResponse:completionHandler:
还是被调用了(暂时还没弄明白怎么回事,按理来说数据来自本地副本,应该不会缓存才是)。
自定义缓存存储路径
可在NSURLSessionConfiguration对象可以设置网络请求的相关缓存
- (void)testDataTask {
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *cachePath = @"MyCache";
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:16384 diskCapacity:268435456 diskPath:cachePath];
defaultConfiguration.URLCache = cache;
defaultConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self.dele delegateQueue:nil];
NSURL *url = [NSURL URLWithString:@"http://115.159.219.141:8000/promotion/list/"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[[session dataTaskWithRequest:request] resume];
}
在如此配置后
在配置好一个downloadtask后,即便还没有resume task,Caches/com.iclound.URL-Section中就增加了MyCache路径

在完成请求后,可发现Caches/com.iclound.URL-Section/MyCache缓存了对应的内容