缓存设计是每个客户端app开发都要考虑的问题。缓存的方案目前比较常使用的有两种:
1.使用系统内置的缓存处理机制(HTTP 缓存协议);
2.开发人员自己制定缓存策略并创建数据库将服务器返回的数据缓存起来,再次请求时根据缓存的策略查询数据库是否存在缓存,并且需要开发人员自己控制缓存的清除。
iOS系统自身也提供了一套缓存机制NSURLCache。
NSURLCache
NSURLCache 是iOS系统提供的实现网络缓存的一个控件,被放于NSURL Loading这个功能控件中。NSURLCache为URL请求提供了内存以及磁盘上的缓存机制,还有缓存策略可以配置。
无论是NSURLConnection、URLSession还是UIWebView、WKWebView(WKWebView缓存是从9.0还是的,iOS8.0中同样包含缓存设计, 但是未提供缓存配置接口)默认都是会使用缓存的。并且默认使用NSURLCache缓存。通过NSURLConnection 加载的请求都会被NSURLCache处理。在NSURLConnection加载系统中,缓存被设计为request对象的一个属性,由NSURLRequest对象的cachePolicy属性指定。而在NSURLSession加载系统中,缓存被设计为NSURLSessionConfiguration对象的一个属性,该属性指定的策略被session的所有request共享。
缓存策略NSURLRequestCachePolicy
NSURLRequestUseProtocolCachePolicy:默认的缓存策略,对于特定的URL请求使用网络协议中实现的缓存逻辑。
NSURLRequestReloadIgnoringLocalCacheData:不使用缓存,请求原始地址加载数据;
NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地缓存,同时也忽略代理服务或其他中间已有的、协议允许的缓存(iOS未实现);
NSURLRequestReloadIgnoringCacheData:同NSURLRequestReloadIgnoringLocalCacheData一致;
NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用缓存数据。如果缓存中没有对应的请求数据,请求原始地址加载数据;
NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用缓存数据,如果缓存中没有对应的请求数据,也不请求原始数据,认为请求失败;
NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存的合法性后,缓存数据才可以使用,否则请求原始数据加载(iOS未实现);
请求头信息 Request Cache Header
- if-Modified-Since:与响应头Last-Modifited相对应,值为最后一次响应头中的Last-Modifited,标识客户端记录的资源最后修改时间。
- if-None-Match:与响应头Etag相对应,值为最后一次响应头中的Etag。
响应头信息 Response Cache Header
- Cache-Control缓存控制
在第一次请求到服务器数据的时候,服务器需要使用Cache-Control这个响应头来指定缓存的策略,格式Cache-Control:max-age=xxxx,这个头包含缓存过期的时间。
max-age:缓存时间,单位秒;
public:可以被任何区缓存,包括中间经过的代理服务器。
private:只能被当前客户端缓存;
no-cache:必须和服务器端确认响应是否发生变化,如果没有就是用缓存,否则更新请求响应数据;
no-store:禁止使用缓存;
除了Cache-Control以外,服务器也可能发送一些附加的头信息,用于根据需求来有条件的请求:
- Last-Modified: 请求资源最近修改时间。
- Etags(Entity tag缩写): 请求资源标识符,主要用于动态生成,没有Last-Modified值的资源。
- Vary: 决定请求是否可以使用缓存,通常作为缓存key值是否唯一的确定因素,同一个资源,不同的Vary设置会被作为两个缓存资源
NSURLRequestUseProtocolCachePolicy
默认缓存策略,当客户端发起一个请求时,先检查本地是否有缓存,如果有,再检查缓存是否过期(通过Cache-Control来判断),没有过期直接使用缓存数据。如果过期,就发起请求给服务器。如果资源具有Last-Modified或者Etags声明,服务器就会对比资源Last-Modified或者Etags,如果不同则返回新数据,否则返回304或者200,告诉客户端使用本地缓存数据。这个过程中客户端发送不发送请求主要取决于max-age是否过期,过期后就重新发起请求,服务器端根据情况通知客户端是否可以继续使用缓存。
默认缓存实现
使用默认缓存,一般不需要对NSURLSession做设置,只需要服务器响应头部加上Cache-Control:max-age=xxxx就行。缓存都是根据request来存储的,如果请求的request中有实时的时间戳等变动的参数,需要把这些参数放在请求的header中,否则会认为不是同一个request,在缓存中就查不到相应的数据。
服务器端不进行缓存设置的话(header中不做Cache-Control相关设置),默认情况下NSURLSession是不会使用缓存数据的。
如果需要设置其他缓存方式,可以在NSURLSessionConguration的requestCachePolicy属性指定具体的缓存策略。
设置缓存的内存、磁盘大小以及存储位置。
NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:0 * 1024 * 1024 diskCapacity:10 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:urlCache];
这段代码可以放到AppDelegate中,在程序启动时就设置好,也可以放在网络请求的单例中,其实设置缓存大小的代码是有默认值的,如果不需要改变缓存的大小及位置,则不需要写这段代码。默认缓存位置存储在:
(user home directory)/Library/Caches/(application bundle id)
获取response数据的header信息:
if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *r = (NSHTTPURLResponse *)task.response;
NSLog(@"header:%@",[r allHeaderFields]);
}
header结果:
header:{
"Cache-Control" = "max-age=60";
Connection = "keep-alive";
"Content-Encoding" = gzip;
"Content-Type" = "text/html;charset=UTF-8";
Date = "Fri, 22 Feb 2019 11:04:23 GMT";
Server = openresty;
"Transfer-Encoding" = Identity;
Vary = "Accept-Encoding";
}
如果需要对缓存进行修改或其他处理,需要实现NSURLProtocol子类:
NSURLSession:除了下载任务,实现URLSession:dataTask:willCacheResponse:completionHandler:方法。
NSURLSession提供一个block来告之会话需要缓存什么东西,而NSURLConnection代理则需要返回一个NSURLCachedResponse对象。NSURLConnection:实现connection:willCacheResponse:方法。
因为NSCachedURLResponse没有可变部分,为了改变cachedResponse中的值必须新创建一个NSURLCachedResponse对象用来缓存,同时也用于下一次的返回。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];
NSMutableData *mutableData = [[cachedResponse data] mutableCopy];
NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly;
// ...
return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
data:mutableData
userInfo:mutableUserInfo
storagePolicy:storagePolicy];
}
如果 -connection:willCacheResponse: 返回 nil,不进行缓存。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
如果不实现这个方法,NSURLConnection就简单的使用本来要传入的缓存对象,所以除非要改变一些值或者阻止缓存,否则这个代理方法无需实现。
以上主要是对NSURLCache缓存策略的简单介绍,有兴趣的可以研究实现。
其他相关知识参考:
http://www.cnblogs.com/chenqf/p/6386163.html
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
https://www.jianshu.com/p/05616e9a1c7f