我们现在在使用WKWebview的时候会设置网络加载的一个策略模式设置NSURLRequestCachePolicy,点击进入会发现有很多不同的样式可供选择使用。这里就简单间接说明一下每一个不同的策略模式。
NSURLRequestCachePolicy
的定义如下
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,//默认遵守http缓存策略
NSURLRequestReloadIgnoringLocalCacheData = 1, //忽略本地缓存
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented //忽略本地和远程缓存
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2,//只有当本地缓存不存在的时候才会请求,否则加载本地缓存
NSURLRequestReturnCacheDataDontLoad = 3,//只加载本地缓存,没有缓存也不会请求
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented //判断缓存是否过期
};
-
NSURLRequestUseProtocolCachePolicy
默认的缓存策略,其行为是由协议指定的针对该协议最好的实现方式。关于该策略的介绍,篇后详细说明。 -
NSURLRequestReloadIgnoringCacheData
从服务端加载数据,完全忽略缓存。 -
NSURLRequestReturnCacheDataElseLoad
使用缓存数据,忽略其过期时间;只有在没有缓存版本的时候才从源端加载数据。 -
NSURLRequestReturnCacheDataDontLoad
只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式
NSURLRequestUseProtocolCachePolicy实现机制
苹果官方提供的决策树如下:
NSURLRequestUseProtocolCachePolicy 对于HTTP和HTTPS的决策树
由图中可以看出,简单流程如下:
- 如果请求的缓存响应不存在,则URL加载系统直接从源端加载数据;
- 否则,如果缓存响应中没有明确表示每次请求必须重新验证,则如果不是响应的缓存过期了,则URL加载系统会返回缓存数据
- 如果缓存的响应过期或者需要重新验证,URL加载系统发送
HEAD
请求到源端,查看资源是否发生了变化。如果变化了,则URL加载系统取出从始发源的数据。否则,它返回缓存的响应。
缓存的响应过期或者需要重新验证说明
对于缓存的响应过期或者需要重新验证的情况,可以通过HTTP中请求和响应头来判断
- Cache-Control
在第一次请求到服务器资源的时候,服务器需要使用Cache-Control这个响应头来指定缓存策略,它的格式如下:Cache-Control:max-age=xxxx,这个头指指明缓存过期的时间Cache-Control
头具有如下选项:
常量 | 意义 |
---|---|
public | 指示响应可被任何缓存区缓存 |
private | 内容只缓存到私有缓存中(仅客户端可以缓存) |
no-cache | 指示请求或响应消息不能缓存 |
no-store | 所有内容都不会被缓存到缓存或 Internet 临时文件中 |
must-revalidation | 如果缓存的内容失效,请求必须发送到服务器进行重新验证 |
max-age | 可以接收生存期不大于指定时间(以秒为单位)的响应 |
min-fresh | 可以接收响应时间小于当前时间加上指定时间的响应 |
max-stale | 可以接收超出超时期间的响应消息 |
-
Expires
Expires
表示存在时间,允许客户端在这个时间之前不去检查(发请求),等同max-age的效果。但是如果同时存在,则被Cache-Control的max-age覆盖。
格式:Expires = "Expires" ":" HTTP-date"
例如:Expires: Thu, 01 Dec 1994 16:00:00 GMT (必须是GMT格式) -
Last-Modified/If-Modified-Since
Last-Modified
是由服务器返回响应头,标识资源的最后修改时间.If-Modified-Since
则由客户端发送,标识客户端所记录的,资源的最后修改时间。服务器接收到带有该请求头的请求时,会使用该时间与资源的最后修改时间进行对比,如果发现资源未被修改过,则直接返回HTTP 304而不返回包体,告诉客户端直接使用本地的缓存。否则响应完整的消息内容。 -
Etag/If-None-Match
Etag
由服务器发送,告之当资源在服务器上的一个唯一标识符。
客户端请求时,如果发现资源过期(使用Cache-Control的max-age),发现资源具有Etag声明,这时请求服务器时则带上If-None-Match头,服务器收到后则与资源的标识进行对比,决定返回200或者304。
借鉴参考文章:html的缓存策略 讲的很详细
参数介绍
ETag
:服务器验证令牌,文件内容hash
。Last-Modified
:响应头标识了资源的最后修改时间。If-None-Match
:比较ETag
是否一致。If-Modified-Since
:比较资源最后修改的时间是否一致。
基本实现原理
- 第一次请求某个html的时候,响应头
response headers
中会返回ETag
和Last-Modified
(需要html做设置),将其记录下来。 - 后面每次请求时,在
request headers
请求头中设置If-None-Match
和If-Modified-Since
,其中If-None-Match
就是记录的ETag
值,If-Modified-Since
就是记录的Last-Modified
值。该值会和服务端的ETag
和Last-Modified
比较。如果相同则返回状态码304
,说明没有更新,否则返回200
,说明需要重新请求。
iOS实现方式
NSURL *url = [NSURL URLWithString:@"http://172.17.124.102:8888/webViewTest.html"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
request.HTTPMethod = @"HEAD";
//获取记录的response headers
NSDictionary *cachedHeaders = [[NSUserDefaults standardUserDefaults] objectForKey:url.absoluteString];
//设置request headers
if (cachedHeaders) {
NSString *etag = [cachedHeaders objectForKey:@"Etag"];
if (etag) {
[request setValue:etag forHTTPHeaderField:@"If-None-Match"];
}
NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
if (lastModified) {
[request setValue:lastModified forHTTPHeaderField:@"If-Modified-Since"];
}
}
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"======= %f",[[NSDate date] timeIntervalSince1970] * 1000);
// 类型转换
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(@"statusCode == %@", @(httpResponse.statusCode));
// 判断响应的状态码
if (httpResponse.statusCode == 304 || httpResponse.statusCode == 0) {
//如果状态码为304或者0(网络不通?),则设置request的缓存策略为读取本地缓存
[request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
}else {
//如果状态码为200,则保存本次的response headers,并设置request的缓存策略为忽略本地缓存,重新请求数据
[[NSUserDefaults standardUserDefaults] setObject:httpResponse.allHeaderFields forKey:request.URL.absoluteString];
//如果状态码为200,则设置request的缓存策略为忽略本地缓存
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
}
//未更新的情况下读取缓存
dispatch_async(dispatch_get_main_queue(), ^{
//判断结束之后,修改请求方式,加载网页
request.HTTPMethod = @"GET";
[self.webView loadRequest:request];
});
}] resume];
在这里,我的实现方式是在每次请求加载之前,先获取html的response headers
响应头(使用'HEAD'请求方式,只获取'response headers',不获取页面),通过返回的状态码
最终确定其缓存策略是读取本地缓存
还是重新加载
。最终达到了预期的效果。