背景
最近關(guān)于web界面偶有反饋拉到舊的界面,導(dǎo)致出現(xiàn)一些異常情況;
因此,對web資源的加載、緩存進(jìn)行一些梳理。
正文
一、緩存相關(guān)概念介紹
NSURLCache是iOS系統(tǒng)常用的web緩存方式,通過
[NSURLCache sharedURLCache]獲取默認(rèn)的緩存相關(guān)信息;可以在啟動(dòng)的時(shí)候,通過[NSURLCache setSharedURLCache:URLCache]的方式設(shè)置一個(gè)自定義的NSURLCache。NSCache和NSURLCache名字相近,其實(shí)沒有什么關(guān)系;NSCache可以認(rèn)為是一個(gè)字典緩存,在內(nèi)存不足的時(shí)候會(huì)自動(dòng)釋放對象。雖然是系統(tǒng)提供的官方緩存類,但是實(shí)際開發(fā)中并沒有使用,替代者是YYCache。
URLProtocol是iOS系統(tǒng)對URL請求行為進(jìn)行抽象,細(xì)化出每一步操作,讓開發(fā)者可以針對每一步進(jìn)行代理,實(shí)現(xiàn)對特定請求的攔截,并返回本地的數(shù)據(jù)。
使用的時(shí)候,首先通過canInitWithRequest:(NSURLRequest *)request,告訴系統(tǒng)要進(jìn)行代理;
然后在startLoading中,通過判斷request和本地緩存信息,判斷本次請求是否可以返回本地?cái)?shù)據(jù),并相應(yīng)調(diào)用client的方法;
舉例,下面就是讀取本地?cái)?shù)據(jù),判斷ETag是否相同,進(jìn)而返回304的邏輯:
NSString *requestETag = request.allHTTPHeaderFields[@"If-None-Match"];
NSString *etag = localData.eTag?:@"";
NSDictionary *headerFields = @{@"Cache-Control" : @"max-age=600", @"ETag":etag, @"Access-Control-Allow-Origin" : @"*"};
if (requestETag.length > 0 && [requestETag isEqualToString:etag]) {
NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:304 HTTPVersion:nil headerFields:headerFields];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
[self.client URLProtocolDidFinishLoading:self];
}
NSURLCache和URLProtocol的差別:
1、NSURLCache只支持GET請求,URLProtocol還支持Post請求;
2、NSURLCache清理緩存通常使用removeAllCachedResponses清理全部緩存,URLProtocol是代理資源加載過程,本地磁盤的資源存儲(chǔ)由業(yè)務(wù)控制;
二、HTTP的緩存機(jī)制
以某個(gè)web界面加載為例,當(dāng)我們不使用瀏覽器緩存時(shí),返回的response是完整的html文本,同時(shí)還附帶著ETag;
如果打開緩存策略,則請求頭帶了If-None-Match(對應(yīng)直接的ETag: "5e58f3dd-b0b"),此時(shí)回包體積明顯變小,同時(shí)返回碼是304;

當(dāng)請求或者response帶有no-cache、max-age=0時(shí),緩存的資源仍可使用,但是會(huì)通過請求進(jìn)行驗(yàn)證,類似上面的ETag,返回304表示Not Modified,可以繼續(xù)使用;(no-cache,并非放棄緩存)
而當(dāng)max-age=3600時(shí),表示資源有效時(shí)間是1個(gè)小時(shí),在有效時(shí)間內(nèi)不需要通過后端驗(yàn)證,此時(shí)不需要發(fā)起網(wǎng)絡(luò)請求,會(huì)直接由cache返回?cái)?shù)據(jù)。(前提是客戶端的request的header,沒有設(shè)置no-cache和max-age=0)
一個(gè)資源的請求流程:

關(guān)于request和response的總結(jié):
- request的header是資源請求的核心控制參數(shù),如果request的cache策略是no-cache或者max-age=0,則一定會(huì)驗(yàn)證資源;
- request沒有設(shè)置cache-control的策略,則按照response的策略進(jìn)行,如果age大于reponse的max-age或者response設(shè)置了no-cache,則會(huì)進(jìn)行資源校驗(yàn);如果reponse設(shè)置了max-age=x,客戶端的age當(dāng)前小于x,則不會(huì)發(fā)起網(wǎng)絡(luò)請求,直接使用cache的數(shù)據(jù);
web同學(xué)表示,web界面通常不會(huì)設(shè)置request的cache-control,因?yàn)殪o態(tài)資源的加載永遠(yuǎn)在js之前;
即使是在html的最前面加上cache-control的<meta>標(biāo)簽,也是在html拉到之后才能生效;
(但是客戶端開發(fā)可以設(shè)置request-header)
三、業(yè)務(wù)緩存邏輯(web緩存SDK)
在前面的client->cache->server基礎(chǔ)上,web緩存SDK所在的層級是在cache和server之間;
cache屬于瀏覽器自身的緩存,web緩存SDK相當(dāng)于代理,阻斷了瀏覽器發(fā)起的網(wǎng)絡(luò)請求,如果本地有匹配的數(shù)據(jù),則使用本地?cái)?shù)據(jù)返回,如果沒有使用網(wǎng)絡(luò)請求,最終所有的數(shù)據(jù)都會(huì)加載到cache;
web緩存SDK和上面的緩存策略并沒有關(guān)系,上面的緩存策略決定是否要發(fā)起網(wǎng)絡(luò)請求去驗(yàn)證資源、加載資源,而web緩存SDK則是在請求發(fā)起之后直接返回,類似charles的map local;

四、一個(gè)歷史教訓(xùn)
線上的web界面出現(xiàn)一個(gè)bug,web的同學(xué)修復(fù)完之后,手動(dòng)刷新了cdn的資源和業(yè)務(wù)緩存SDK的資源。
但是部分html配置的no-cache失效(設(shè)置了max-age=xxx),導(dǎo)致如果之前進(jìn)入過在拉到之前,會(huì)使用瀏覽器緩存;導(dǎo)致本次啟動(dòng)會(huì)一直使用舊的的界面。
解決方案:
1、更換該界面的url,使得cache失效;
2、清除webKit的緩存;
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate dateWithTimeIntervalSince1970:0] completionHandler:^{
//清除靜態(tài)資源成功
}];
總結(jié)
HTTP協(xié)議的學(xué)問博大精深,這次借此對緩存相關(guān)知識進(jìn)行一次梳理。
如有紕漏,歡迎指正;如有關(guān)于緩存的使用建議,歡迎交流。
參考鏈接
https://stackoverflow.com/questions/27105094/how-to-remove-cache-in-wkwebview