WKWebView缓存总结

8,314 阅读6分钟

近期公司楼下实体店的网络及其不稳定,经常有用户反馈App里的网页打开特别慢,进度条一直加载不完,体验很差,于是就有了webview缓存的需求,项目里使用的是WKWebView,而且苹果早就不提倡使用UIWebView了,这里也不做赘述了。

WKWebView 支持的缓存策略枚举

* 参见 苹果官方文档

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0, // 默认策略,具体的缓存逻辑和协议的声明有关,如果协议没有声明,不需要每次重新验证cache。
    NSURLRequestReloadIgnoringLocalCacheData = 1, // 忽略本地缓存,直接从后台请求数据
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, //  iOS 13 实现,忽略本地缓存数据、代理和其他中介的缓存,直接从后台请求数据
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData        
    NSURLRequestReturnCacheDataElseLoad = 2, // // 优先从本地拿数据,且忽略请求生命时长和过期时间。但是如果没有本地cache,则请求源数据
    NSURLRequestReturnCacheDataDontLoad = 3,  //只从本地拿数据
    NSURLRequestReloadRevalidatingCacheData = 5, // iOS 13才实现,从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
};

WKWebView使用缓存方式:

// 使用默认策略举例
NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:self.urlString]
                                                               cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                           timeoutInterval:20];
[_webView loadRequest:request];

我们需要注意一下 NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4 和  NSURLRequestReloadRevalidatingCacheData =5 在iOS13之前并未实现该协议,所以如果使用该协议一定要注意系统版本判断。官方文档说明:


WKWebView 默认的缓存策略 NSURLRequestUseProtocolCachePolicy = 0

官方文档关于HTTP和HTTPS的NSURLRequestUseProtocolCachePolicy决策树如下图:


官方文档对默认缓存策略的说明:

  1. 如果缓存不存在,则向服务器发起请求;
  2. 如果缓存存在,且缓存response头没有指明每次都必须校验资源更新,且缓存没有过期,则系统会直接返回缓存,不会发起请求;
  3. 如果缓存过期了或者要求每次请求都必须校验资源更新,则发起一个校验资源的请求,如果(服务器返回)资源有更新则使用服务器返回的最新数据,如果没有更新则使用本地缓存。

如果没有特殊需求,系统默认的缓存策略已经比较完善了。

客户端使用系统缓存

  1. 设置缓存策略,决定是否使用缓存(即使允许加载缓存,离线的时候,也只能显示页面,具体的数据缓存需要web端实现);
  2. 使用 NSURLProtocol 拦截js、css,图片资源,但这种方式有一个问题,NSURLProtocol使用了私有API,有审核被拒的风险,项目中暂不推荐使用;
  3. 同服务器进行交互(缓存过期处理,以及web资源更新)。

WKWebView默认缓存策略遵循HTTP缓存协议,客户端默认缓存行为实际上是由服务器控制的,客户端和服务器通过HTTP请求头和响应头中的缓存字段来交流,进而影响客户端的行为。下面几个相关的请求头和响应头需要知道:

  • Cache-Control:max-age=xxxx,指明缓存过期时间

在第一次请求到服务器资源的时候,服务器需要使用Cache-Control这个响应头来指定缓存策略,它的格式如下:Cache-Control:max-age=xxxx,这个头指明缓存过期的时间

  • 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。


设置缓存策略代码:

// 默认缓存策略 
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15];

//自定义缓存策略
//判断是否有更新,只请求responseHeader
        request.HTTPMethod = @"HEAD";
        //获取记录的response headers
        NSDictionary *cachedHeaders = [[NSUserDefaults standardUserDefaults] objectForKey:url];
        //设置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 * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            // 类型转换
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            NSLog(@"httpResponse == %@", httpResponse);
            // 判断响应的状态码
            if (httpResponse.statusCode == 304 || httpResponse.statusCode == 0) {
                //如果状态码为304或者0(网络不通?),则设置request的缓存策略为读取本地缓存
                [request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
            }else {
                //如果状态码为200,则保存本次的response headers,并设置request的缓存策略为忽略本地缓存,重新请求数据
                [[NSUserDefaults standardUserDefaults] setObject:httpResponse.allHeaderFields forKey:url];
                //如果状态码为200,则设置request的缓存策略为默认缓存策略
                [request setCachePolicy:NSURLRequestUseProtocolCachePolicy];
            }
            //未更新的情况下读取缓存,更新会重新请求
            dispatch_async(dispatch_get_main_queue(), ^{
                //判断结束之后,修改请求方式,加载网页
                request.HTTPMethod = @"GET";
                [self loadRequest:request];
            });
       }] resume];


客户端WebView加载H5和服务器交互过程流程图:



WKWebView缓存沙盒路径:



WKWebView清除缓存

WKWebView,在iOS9以后提供了缓存管理类WKWebsiteDataStore,iOS9以前只能手动移除文件

WKWebsiteDataStore 提供了API获取web缓存数据类型

[WKWebsiteDataStore allWebsiteDataTypes];

WKWebView支持的缓存类型:

 /**
    清除WKWebView的缓存
     
    在磁盘缓存上。
    WKWebsiteDataTypeDiskCache,
    
    html离线Web应用程序缓存。
    WKWebsiteDataTypeOfflineWebApplicationCache,
    
    内存缓存。
    WKWebsiteDataTypeMemoryCache,
    
    本地存储。
    WKWebsiteDataTypeLocalStorage,
    
    Cookies
    WKWebsiteDataTypeCookies,
    
    会话存储
    WKWebsiteDataTypeSessionStorage,
    
    IndexedDB数据库。
    WKWebsiteDataTypeIndexedDBDatabases,
    
    查询数据库。
    WKWebsiteDataTypeWebSQLDatabases
    */

WKWebView 清除指定类型缓存:

NSSet *websiteDataTypes= [NSSet setWithArray:@[

                        WKWebsiteDataTypeDiskCache,
                        WKWebsiteDataTypeMemoryCache
                        ]];

//清除所有的web信息
//NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{

}];


拓展阅读:

苹果官方文档

WKWebView默认缓存策略与HTTP缓存协议

iOS WKWebView (NSURLProtocol)拦截js、css,图片资源

iOS网络缓存扫盲篇--使用两行代码就能完成80%的缓存需求