
iOS webView緩存,保證加載最新html
[TOC]
前言
最近有個需求,修改webview(WKWebview)加載的緩存機制。因現(xiàn)在使用的緩存機制是NSURLRequestReturnCacheDataElseLoad(NSURLRequest的緩存機制下面會說到)。這個緩存機制就是只有當(dāng)本地緩存不存在的時候才會請求,否則加載本地緩存,這樣就導(dǎo)致當(dāng)html有所修改的話,下次進入不能主動刷新網(wǎng)頁,還是加載的緩存,需要手動刷新才能看到最新內(nèi)容。現(xiàn)在的要求就是當(dāng):當(dāng)html過期后(html有修改),在下次主動加載html的時候自動加載最新內(nèi)容。
尋找解決方案
1. 查看NSURLRequest的API
既然之前使用了NSURLRequest的緩存機制,那么首先想到的就是看看有沒有對應(yīng)的緩存機制。
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,//默認遵守http緩存策略
NSURLRequestReloadIgnoringLocalCacheData = 1, //忽略本地緩存
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented //忽略本地和遠程緩存
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2,//只有當(dāng)本地緩存不存在的時候才會請求,否則加載本地緩存
NSURLRequestReturnCacheDataDontLoad = 3,//只加載本地緩存,沒有緩存也不會請求
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented //判斷緩存是否過期
};
忽略Unimplemented,可以看到NSURLRequestReloadRevalidatingCacheData不正是我們需要的緩存策略嗎?當(dāng)你高高興興的將緩存策略設(shè)置為NSURLRequestReloadRevalidatingCacheData后,然后加載html,然后修改html內(nèi)容,發(fā)現(xiàn)確實會加載最新的。這個時候你一定會很高心,然而當(dāng)你打印html加載時間的時候,你會發(fā)現(xiàn)html未修改的情況下和不加載緩存所用的時間都是一樣的,其結(jié)論就是并沒有加載緩存。這個時候你再看Unimplemented就會煥然大悟了。
所以通過修改NSURLRequest的緩存策略是無法實現(xiàn)該功能的,pass
2. 網(wǎng)上搜索webView的緩存加載策略
通過設(shè)置NSURLRequest的緩存機制無法達到我們的目的。沒辦法,只有找其他的方法了。
在查看了很多篇相關(guān)的技術(shù)博客后,終于找到了一個方法,就是設(shè)置ETag/If-None-Match和Last-Modified/If-Modified-Since來判斷html內(nèi)容是否有更新。其中If-None-Match和If-Modified-Since是設(shè)置在request headers請求頭中,ETag和Last-Modified是response headers響應(yīng)頭中,由服務(wù)器返回的。
參數(shù)介紹
-
ETag:服務(wù)器驗證令牌,文件內(nèi)容hash。 -
Last-Modified:響應(yīng)頭標(biāo)識了資源的最后修改時間。 -
If-None-Match:比較ETag是否一致。 -
If-Modified-Since:比較資源最后修改的時間是否一致。
關(guān)于html的緩存策略可以看看這篇博客,講的很詳細
基本實現(xiàn)原理
- 第一次請求某個html的時候,響應(yīng)頭
response headers中會返回ETag和Last-Modified(需要html做設(shè)置),將其記錄下來。 - 后面每次請求時,在
request headers請求頭中設(shè)置If-None-Match和If-Modified-Since,其中If-None-Match就是記錄的ETag值,If-Modified-Since就是記錄的Last-Modified值。該值會和服務(wù)端的ETag和Last-Modified比較。如果相同則返回狀態(tài)碼304,說明沒有更新,否則返回200,說明需要重新請求。
iOS實現(xiàn)方式
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];
//設(shè)置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);
// 類型轉(zhuǎn)換
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(@"statusCode == %@", @(httpResponse.statusCode));
// 判斷響應(yīng)的狀態(tài)碼
if (httpResponse.statusCode == 304 || httpResponse.statusCode == 0) {
//如果狀態(tài)碼為304或者0(網(wǎng)絡(luò)不通?),則設(shè)置request的緩存策略為讀取本地緩存
[request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
}else {
//如果狀態(tài)碼為200,則保存本次的response headers,并設(shè)置request的緩存策略為忽略本地緩存,重新請求數(shù)據(jù)
[[NSUserDefaults standardUserDefaults] setObject:httpResponse.allHeaderFields forKey:request.URL.absoluteString];
//如果狀態(tài)碼為200,則設(shè)置request的緩存策略為忽略本地緩存
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
}
//未更新的情況下讀取緩存
dispatch_async(dispatch_get_main_queue(), ^{
//判斷結(jié)束之后,修改請求方式,加載網(wǎng)頁
request.HTTPMethod = @"GET";
[self.webView loadRequest:request];
});
}] resume];
在這里,我的實現(xiàn)方式是在每次請求加載之前,先獲取html的response headers響應(yīng)頭(使用'HEAD'請求方式,只獲取'response headers',不獲取頁面),通過返回的狀態(tài)碼最終確定其緩存策略是讀取本地緩存還是重新加載。最終達到了預(yù)期的效果。
最后
雖然通過這個方式實現(xiàn)了該功能,但是在實現(xiàn)過程中還是有一些東西沒有弄懂。
比如:
- 在
webView通過loadRequest加載html的時候,設(shè)置了request headers,然后在WKWebView的代理方法webView:didFinishNavigation:方法中獲取的狀態(tài)碼永遠是200,response headers響應(yīng)頭在修改了html內(nèi)容后都沒有變化,這里獲取到的數(shù)據(jù)和通過NSURLSession獲取到的有什么不同。
2. 還有就是這種方式獲取使用HEAD請求可以避免網(wǎng)頁的二次下載,只請求響應(yīng)頭數(shù)據(jù),謝謝王洪亮ios的提醒。狀態(tài)碼及response headers響應(yīng)頭其實相當(dāng)于在加載之前重新請求了一下。
不知道有沒有更好的方法來實現(xiàn)該功能,歡迎討論和指正。