目錄
1.當(dāng)我們?cè)谡務(wù)摼彺娴臅r(shí)候,我們?cè)谡務(wù)撌裁矗?/p>
2.GET網(wǎng)絡(luò)請(qǐng)求緩存
i.80%的緩存需求:兩行代碼就可滿足
ii.控制緩存的有效性
iii.文件緩存:借助ETag或Last-Modified判斷文件緩存是否有效
a.Last-Modified
b.ETag
c.總結(jié)
iv.一般數(shù)據(jù)類(lèi)型借助 Last-Modified 與 ETag 進(jìn)行緩存
3.剩下20%的網(wǎng)絡(luò)緩存需求--真的有NSURLCache 不能滿足的需求?
由于微信、QQ、微博、這類(lèi)的應(yīng)用使用緩存很“重”,使一般的用戶也對(duì)緩存也非常習(xí)慣。緩存已然成為必備。
“緩存的目的的以空間換時(shí)間”
這句話在動(dòng)輒就是 300M、600M 的大應(yīng)用上,得到了很好的詮釋。但能有緩存意識(shí)的公司,還在少數(shù)。
“只有你真正感受到痛的時(shí)候,你才會(huì)考慮使用緩存。”
這個(gè)痛可能是:
服務(wù)器壓力、客戶端網(wǎng)絡(luò)優(yōu)化、用戶體驗(yàn)等等。
當(dāng)我們?cè)谡務(wù)摼彺娴臅r(shí)候,我們?cè)谡務(wù)撌裁矗?/b>
我們今天將站在小白用戶的角度,給緩存這個(gè)概念進(jìn)行重新的定義。
緩存有不同的分類(lèi)方法:
這里所指的緩存,是一個(gè)寬泛的概念。
我們這里主要按照功能進(jìn)行劃分:
GET網(wǎng)絡(luò)請(qǐng)求緩存
概述
首先要知道,POST請(qǐng)求不能被緩存,只有 GET 請(qǐng)求能被緩存。因?yàn)閺臄?shù)學(xué)的角度來(lái)講,GET 的結(jié)果是?冪等?的,就好像字典里的 key 與 value 就是冪等的,而 POST 不?冪等?。緩存的思路就是將查詢的參數(shù)組成的值作為 key ,對(duì)應(yīng)結(jié)果作為value。從這個(gè)意義上說(shuō),一個(gè)文件的資源鏈接,也叫 GET 請(qǐng)求,下文也會(huì)這樣看待。
80%的緩存需求:兩行代碼就可滿足
設(shè)置緩存只需要三個(gè)步驟:
第一個(gè)步驟:請(qǐng)使用 GET 請(qǐng)求。
第二個(gè)步驟:
如果你已經(jīng)使用 了 GET 請(qǐng)求,iOS 系統(tǒng) SDK 已經(jīng)幫你做好了緩存。你需要的僅僅是設(shè)置下內(nèi)存緩存大小、磁盤(pán)緩存大小、以及緩存路徑。甚至這兩行代碼不設(shè)置也是可以的,會(huì)有一個(gè)默認(rèn)值。代碼如下:
1
2NSURLCache?*urlCache?=?[[NSURLCache?alloc]?initWithMemoryCapacity:4?*?1024?*?1024?diskCapacity:20?*?1024?*?1024?diskPath:nil];
[NSURLCache?setSharedURLCache:urlCache];
第三個(gè)步驟:沒(méi)有第三步!
你只要設(shè)置了這兩行代碼,基本就可滿足80%的緩存需求。AFNetworking 的作者 Mattt曾經(jīng)說(shuō)過(guò):
“無(wú)數(shù)開(kāi)發(fā)者嘗試自己做一個(gè)簡(jiǎn)陋而脆弱的系統(tǒng)來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)緩存的功能,殊不知?NSURLCache?只要兩行代碼就能搞定且好上 100 倍。”
(AFN 是不是在暗諷 SDWebImage 復(fù)雜又蹩腳的緩存機(jī)制??)
要注意
iOS 5.0開(kāi)始,支持磁盤(pán)緩存,但僅支持 HTTP。
iOS 6.0開(kāi)始,支持 HTTPS 緩存。
控制緩存的有效性
我們知道:
只要是緩存,總會(huì)過(guò)期。
那么緩存的過(guò)期時(shí)間如何控制?
上文中的兩行代碼,已經(jīng)給出了一個(gè)方法,指定超時(shí)時(shí)間。但這并也許不能滿足我們的需求,如果我們對(duì)數(shù)據(jù)的一致性,時(shí)效性要求很高,即使1秒鐘后數(shù)據(jù)更改了,客戶端也必須展示更改后的數(shù)據(jù)。這種情況如何處理?
下面我們將對(duì)這種需求,進(jìn)行解決方案的介紹。順序是這樣的:先從文件類(lèi)型的緩存入手,引入兩個(gè)概念。然后再談下,一般數(shù)據(jù)類(lèi)型比如 JSON 返回值的緩存處理。
文件緩存:借助ETag或Last-Modified判斷文件緩存是否有效。
Last-Modified
服務(wù)器的文件存貯,大多采用資源變動(dòng)后就重新生成一個(gè)鏈接的做法。而且如果你的文件存儲(chǔ)采用的是第三方的服務(wù),比如七牛、青云等服務(wù),則一定是如此。
這種做法雖然是推薦做法,但同時(shí)也不排除不同文件使用同一個(gè)鏈接。那么如果服務(wù)端的file更改了,本地已經(jīng)有了緩存。如何更新緩存?
這種情況下需要借助?ETag?或?Last-Modified?判斷圖片緩存是否有效。
Last-Modified?顧名思義,是資源最后修改的時(shí)間戳,往往與緩存時(shí)間進(jìn)行對(duì)比來(lái)判斷緩存是否過(guò)期。
在瀏覽器第一次請(qǐng)求某一個(gè)URL時(shí),服務(wù)器端的返回狀態(tài)會(huì)是200,內(nèi)容是你請(qǐng)求的資源,同時(shí)有一個(gè)Last-Modified的屬性標(biāo)記此文件在服務(wù)期端最后被修改的時(shí)間,格式類(lèi)似這樣:
Last-Modified: Fri, 12 May 2006 18:53:33 GMT
客戶端第二次請(qǐng)求此URL時(shí),根據(jù) HTTP 協(xié)議的規(guī)定,瀏覽器會(huì)向服務(wù)器傳送 If-Modified-Since 報(bào)頭,詢問(wèn)該時(shí)間之后文件是否有被修改過(guò):
If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
總結(jié)下來(lái)它的結(jié)構(gòu)如下:
如果服務(wù)器端的資源沒(méi)有變化,則自動(dòng)返回 HTTP 304 (Not Changed.)狀態(tài)碼,內(nèi)容為空,這樣就節(jié)省了傳輸數(shù)據(jù)量。當(dāng)服務(wù)器端代碼發(fā)生改變或者重啟服務(wù)器時(shí),則重新發(fā)出資源,返回和第一次請(qǐng)求時(shí)類(lèi)似。從而保證不向客戶端重復(fù)發(fā)出資源,也保證當(dāng)服務(wù)器有變化時(shí),客戶端能夠得到最新的資源。
判斷方法用偽代碼表示:
ifETagFromServer?!=?ETagOnClient?||?LastModifiedFromServer?!=?LastModifiedOnClient
GetFromServer
else
GetFromCache
之所以使用
1
LastModifiedFromServer?!=?LastModifiedOnClient
而非使用:
1
LastModifiedFromServer?>?LastModifiedOnClient
原因是考慮到可能出現(xiàn)類(lèi)似下面的情況:服務(wù)端可能對(duì)資源文件,廢除其新版,回滾啟用舊版本,此時(shí)的情況是:
LastModifiedFromServer?<=?LastModifiedOnClient
但我們依然要更新本地緩存。
參考鏈接:What takes precedence: the ETag or Last-Modified HTTP header?
Demo10和 Demo11 給出了一個(gè)完整的校驗(yàn)步驟:
并給出了?NSURLConnection?和?NSURLSession?兩個(gè)版本:
@brief?如果本地緩存資源為最新,則使用使用本地緩存。如果服務(wù)器已經(jīng)更新或本地?zé)o緩存則從服務(wù)器請(qǐng)求資源。
@details
步驟:
1.?請(qǐng)求是可變的,緩存策略要每次都從服務(wù)器加載
2.?每次得到響應(yīng)后,需要記錄住?LastModified
3.?下次發(fā)送請(qǐng)求的同時(shí),將LastModified一起發(fā)送給服務(wù)器(由服務(wù)器比較內(nèi)容是否發(fā)生變化)
@return?圖片資源
*/
-?(void)getData:(GetDataCompletion)completion?{
NSURL?*url?=?[NSURL?URLWithString:kLastModifiedImageURL];
NSMutableURLRequest?*request?=?[NSMutableURLRequest?requestWithURL:url?cachePolicy:NSURLRequestReloadIgnoringCacheData?timeoutInterval:15.0];
//????//?發(fā)送?etag
//????if?(self.etag.length?>?0)?{
//????????[request?setValue:self.etag?forHTTPHeaderField:@"If-None-Match"];
//????}
//?發(fā)送?LastModified
if(self.localLastModified.length?>?0)?{
[request?setValue:self.localLastModified?forHTTPHeaderField:@"If-Modified-Since"];
}
[[[NSURLSession?sharedSession]?dataTaskWithRequest:request?completionHandler:^(NSData?*data,?NSURLResponse?*response,?NSError?*error)?{
//?NSLog(@"%@?%tu",?response,?data.length);
//?類(lèi)型轉(zhuǎn)換(如果將父類(lèi)設(shè)置給子類(lèi),需要強(qiáng)制轉(zhuǎn)換)
NSHTTPURLResponse?*httpResponse?=?(NSHTTPURLResponse?*)response;
NSLog(@"statusCode?==?%@",?@(httpResponse.statusCode));
//?判斷響應(yīng)的狀態(tài)碼是否是?304?Not?Modified?(更多狀態(tài)碼含義解釋?zhuān)?a target="_blank" rel="nofollow">https://github.com/ChenYilong/iOSDevelopmentTips)
if(httpResponse.statusCode?==?304)?{
NSLog(@"加載本地緩存圖片");
//?如果是,使用本地緩存
//?根據(jù)請(qǐng)求獲取到`被緩存的響應(yīng)`!
NSCachedURLResponse?*cacheResponse?=??[[NSURLCache?sharedURLCache]?cachedResponseForRequest:request];
//?拿到緩存的數(shù)據(jù)
data?=?cacheResponse.data;
}
//?獲取并且紀(jì)錄?etag,區(qū)分大小寫(xiě)
//????????self.etag?=?httpResponse.allHeaderFields[@"Etag"];
//?獲取并且紀(jì)錄?LastModified
self.localLastModified?=?httpResponse.allHeaderFields[@"Last-Modified"];
//????????NSLog(@"%@",?self.etag);
NSLog(@"%@",?self.localLastModified);
dispatch_async(dispatch_get_main_queue(),?^{
!completion??:?completion(data);
});
}]?resume];
}
ETag
ETag?是什么?
HTTP 協(xié)議規(guī)格說(shuō)明定義ETag為“被請(qǐng)求變量的實(shí)體值” (參見(jiàn) —— 章節(jié) 14.19)。 另一種說(shuō)法是,ETag是一個(gè)可以與Web資源關(guān)聯(lián)的記號(hào)(token)。它是一個(gè) hash 值,用作 Request 緩存請(qǐng)求頭,每一個(gè)資源文件都對(duì)應(yīng)一個(gè)唯一的?ETag?值, 服務(wù)器單獨(dú)負(fù)責(zé)判斷記號(hào)是什么及其含義,并在HTTP響應(yīng)頭中將其傳送到客戶端,以下是服務(wù)器端返回的格式:
ETag: "50b1c1d4f775c61:df3"
客戶端的查詢更新格式是這樣的:
If-None-Match: W/"50b1c1d4f775c61:df3"
其中:
If-None-Match?- 與響應(yīng)頭的 Etag 相對(duì)應(yīng),可以判斷本地緩存數(shù)據(jù)是否發(fā)生變化
如果ETag沒(méi)改變,則返回狀態(tài)304然后不返回,這也和Last-Modified一樣。
總結(jié)下來(lái)它的結(jié)構(gòu)如下:
ETag?是的功能與?Last-Modified?類(lèi)似:服務(wù)端不會(huì)每次都會(huì)返回文件資源。客戶端每次向服務(wù)端發(fā)送上次服務(wù)器返回的ETag?值,服務(wù)器會(huì)根據(jù)客戶端與服務(wù)端的?ETag?值是否相等,來(lái)決定是否返回 data,同時(shí)總是返回對(duì)應(yīng)的?HTTP?狀態(tài)碼。客戶端通過(guò)?HTTP?狀態(tài)碼來(lái)決定是否使用緩存。比如:服務(wù)端與客戶端的?ETag?值相等,則?HTTP?狀態(tài)碼為 304,不返回 data。服務(wù)端文件一旦修改,服務(wù)端與客戶端的?ETag?值不等,并且狀態(tài)值會(huì)變?yōu)?00,同時(shí)返回 data。
因?yàn)樾薷馁Y源文件后該值會(huì)立即變更。這也決定了?ETag?在斷點(diǎn)下載時(shí)非常有用。 比如 AFNetworking 在進(jìn)行斷點(diǎn)下載時(shí),就是借助它來(lái)檢驗(yàn)數(shù)據(jù)的。詳見(jiàn)在?AFHTTPRequestOperation?類(lèi)中的用法:
//下載暫停時(shí)提供斷點(diǎn)續(xù)傳功能,修改請(qǐng)求的HTTP頭,記錄當(dāng)前下載的文件位置,下次可以從這個(gè)位置開(kāi)始下載。
-?(void)pause?{
unsigned?long?long?offset?=?0;
if([self.outputStream?propertyForKey:NSStreamFileCurrentOffsetKey])?{
offset?=?[[self.outputStream?propertyForKey:NSStreamFileCurrentOffsetKey]?unsignedLongLongValue];
}else{
offset?=?[[self.outputStream?propertyForKey:NSStreamDataWrittenToMemoryStreamKey]?length];
}
NSMutableURLRequest?*mutableURLRequest?=?[self.request?mutableCopy];
if([self.response?respondsToSelector:@selector(allHeaderFields)]?&&?[[self.response?allHeaderFields]?valueForKey:@"ETag"])?{
//若請(qǐng)求返回的頭部有ETag,則續(xù)傳時(shí)要帶上這個(gè)ETag,
//ETag用于放置文件的唯一標(biāo)識(shí),比如文件MD5值
//續(xù)傳時(shí)帶上ETag服務(wù)端可以校驗(yàn)相對(duì)上次請(qǐng)求,文件有沒(méi)有變化,
//若有變化則返回200,回應(yīng)新文件的全數(shù)據(jù),若無(wú)變化則返回206續(xù)傳。
[mutableURLRequest?setValue:[[self.response?allHeaderFields]?valueForKey:@"ETag"]?forHTTPHeaderField:@"If-Range"];
}
//給當(dāng)前request加Range頭部,下次請(qǐng)求帶上頭部,可以從offset位置繼續(xù)下載
[mutableURLRequest?setValue:[NSString?stringWithFormat:@"bytes=%llu-",?offset]?forHTTPHeaderField:@"Range"];
self.request?=?mutableURLRequest;
[superpause];
}
七牛等第三方文件存儲(chǔ)商現(xiàn)在都已經(jīng)支持ETag,Demo8和9 中給出的演示圖片就是使用的七牛的服務(wù),見(jiàn):
static?NSString?*const?kETagImageURL?=?@"http://ac-g3rossf7.clouddn.com/xc8hxXBbXexA8LpZEHbPQVB.jpg";
下面使用一個(gè) Demo 來(lái)進(jìn)行演示用法,
以?NSURLConnection?搭配?ETag?為例,步驟如下:
請(qǐng)求的緩存策略使用?NSURLRequestReloadIgnoringCacheData,忽略本地緩存
服務(wù)器響應(yīng)結(jié)束后,要記錄?Etag,服務(wù)器內(nèi)容和本地緩存對(duì)比是否變化的重要依據(jù)
在發(fā)送請(qǐng)求時(shí),設(shè)置?If-None-Match,并且傳入?Etag
連接結(jié)束后,要判斷響應(yīng)頭的狀態(tài)碼,如果是?304,說(shuō)明本地緩存內(nèi)容沒(méi)有發(fā)生變化
以下代碼詳見(jiàn) Demo08 :
@brief?如果本地緩存資源為最新,則使用使用本地緩存。如果服務(wù)器已經(jīng)更新或本地?zé)o緩存則從服務(wù)器請(qǐng)求資源。
@details
步驟:
1.?請(qǐng)求是可變的,緩存策略要每次都從服務(wù)器加載
2.?每次得到響應(yīng)后,需要記錄住?etag
3.?下次發(fā)送請(qǐng)求的同時(shí),將etag一起發(fā)送給服務(wù)器(由服務(wù)器比較內(nèi)容是否發(fā)生變化)
@return?圖片資源
*/
-?(void)getData:(GetDataCompletion)completion?{
NSURL?*url?=?[NSURL?URLWithString:kETagImageURL];
NSMutableURLRequest?*request?=?[NSMutableURLRequest?requestWithURL:url?cachePolicy:NSURLRequestReloadIgnoringCacheData?timeoutInterval:15.0];
//?發(fā)送?etag
if(self.etag.length?>?0)?{
[request?setValue:self.etag?forHTTPHeaderField:@"If-None-Match"];
}
[NSURLConnection?sendAsynchronousRequest:request?queue:[NSOperationQueue?mainQueue]?completionHandler:^(NSURLResponse?*response,?NSData?*data,?NSError?*connectionError)?{
//?NSLog(@"%@?%tu",?response,?data.length);dd
//?類(lèi)型轉(zhuǎn)換(如果將父類(lèi)設(shè)置給子類(lèi),需要強(qiáng)制轉(zhuǎn)換)
NSHTTPURLResponse?*httpResponse?=?(NSHTTPURLResponse?*)response;
NSLog(@"statusCode?==?%@",?@(httpResponse.statusCode));
//?判斷響應(yīng)的狀態(tài)碼是否是?304?Not?Modified?(更多狀態(tài)碼含義解釋?zhuān)?a target="_blank" rel="nofollow">https://github.com/ChenYilong/iOSDevelopmentTips)
if(httpResponse.statusCode?==?304)?{
NSLog(@"加載本地緩存圖片");
//?如果是,使用本地緩存
//?根據(jù)請(qǐng)求獲取到`被緩存的響應(yīng)`!
NSCachedURLResponse?*cacheResponse?=??[[NSURLCache?sharedURLCache]?cachedResponseForRequest:request];
//?拿到緩存的數(shù)據(jù)
data?=?cacheResponse.data;
}
//?獲取并且紀(jì)錄?etag,區(qū)分大小寫(xiě)
self.etag?=?httpResponse.allHeaderFields[@"Etag"];
NSLog(@"etag值%@",?self.etag);
!completion??:?completion(data);
}];
}
相應(yīng)的?NSURLSession?搭配 ETag 的版本見(jiàn) Demo09:
@brief?如果本地緩存資源為最新,則使用使用本地緩存。如果服務(wù)器已經(jīng)更新或本地?zé)o緩存則從服務(wù)器請(qǐng)求資源。
@details
步驟:
1.?請(qǐng)求是可變的,緩存策略要每次都從服務(wù)器加載
2.?每次得到響應(yīng)后,需要記錄住?etag
3.?下次發(fā)送請(qǐng)求的同時(shí),將etag一起發(fā)送給服務(wù)器(由服務(wù)器比較內(nèi)容是否發(fā)生變化)
@return?圖片資源
*/
-?(void)getData:(GetDataCompletion)completion?{
NSURL?*url?=?[NSURL?URLWithString:kETagImageURL];
NSMutableURLRequest?*request?=?[NSMutableURLRequest?requestWithURL:url?cachePolicy:NSURLRequestReloadIgnoringCacheData?timeoutInterval:15.0];
//?發(fā)送?etag
if(self.etag.length?>?0)?{
[request?setValue:self.etag?forHTTPHeaderField:@"If-None-Match"];
}
[[[NSURLSession?sharedSession]?dataTaskWithRequest:request?completionHandler:^(NSData?*data,?NSURLResponse?*response,?NSError?*error)?{
//?NSLog(@"%@?%tu",?response,?data.length);
//?類(lèi)型轉(zhuǎn)換(如果將父類(lèi)設(shè)置給子類(lèi),需要強(qiáng)制轉(zhuǎn)換)
NSHTTPURLResponse?*httpResponse?=?(NSHTTPURLResponse?*)response;
NSLog(@"statusCode?==?%@",?@(httpResponse.statusCode));
//?判斷響應(yīng)的狀態(tài)碼是否是?304?Not?Modified?(更多狀態(tài)碼含義解釋?zhuān)?a target="_blank" rel="nofollow">https://github.com/ChenYilong/iOSDevelopmentTips)
if(httpResponse.statusCode?==?304)?{
NSLog(@"加載本地緩存圖片");
//?如果是,使用本地緩存
//?根據(jù)請(qǐng)求獲取到`被緩存的響應(yīng)`!
NSCachedURLResponse?*cacheResponse?=??[[NSURLCache?sharedURLCache]?cachedResponseForRequest:request];
//?拿到緩存的數(shù)據(jù)
data?=?cacheResponse.data;
}
//?獲取并且紀(jì)錄?etag,區(qū)分大小寫(xiě)
self.etag?=?httpResponse.allHeaderFields[@"Etag"];
NSLog(@"%@",?self.etag);
dispatch_async(dispatch_get_main_queue(),?^{
!completion??:?completion(data);
});
}]?resume];
}
運(yùn)行效果:
總結(jié)
在官方給出的文檔中提出?ETag?是首選的方式,優(yōu)于?Last-Modified?方式。因?yàn)?ETag?是基于 hash ,hash 的規(guī)則可以自己設(shè)置,而且是基于一致性,是“強(qiáng)校驗(yàn)”。?Last-Modified?是基于時(shí)間,是弱校驗(yàn),弱在哪里?比如說(shuō):如果服務(wù)端的資源回滾客戶端的?Last-Modified?反而會(huì)比服務(wù)端還要新。
雖然?ETag?優(yōu)于?Last-Modified,但并非所有服務(wù)端都會(huì)支持,而?Last-Modified?則一般都會(huì)有該字段。 大多數(shù)情況下需要與服務(wù)端進(jìn)行協(xié)調(diào)支持?ETag,如果協(xié)商無(wú)果就只能退而求其次。
Demo 也給出了一個(gè)不支持?ETag?的鏈接,基本隨便找一張圖片都行:
1
static?NSString?*const?kLastModifiedImageURL?=?@"http://image17-c.poco.cn/mypoco/myphoto/20151211/16/17338872420151211164742047.png";
作為通用型的網(wǎng)絡(luò)請(qǐng)求工具 AFNetworking 對(duì)該現(xiàn)狀的處理方式是,判斷服務(wù)端是否包含?ETag?,然后再進(jìn)行相應(yīng)處理。可見(jiàn)AFHTTPRequestOperation?類(lèi)中的用法,也就是上文中已經(jīng)給出的斷點(diǎn)下載的代碼。
在回顧下思路:
為資源分派 hash 值,然后對(duì)比服務(wù)端與本地緩存是否一致來(lái)決定是否需要更新緩存。
這種思路,在開(kāi)發(fā)中經(jīng)常使用,比如:處于安全考慮,登陸操作一般不會(huì)傳輸賬號(hào)密碼,而是傳輸對(duì)應(yīng)的 hash 值-- token ,這里的 token 就可以看做一個(gè) file 資源,如果想讓一個(gè)用戶登陸超時(shí)時(shí)間是三天,只需要在服務(wù)端每隔三天更改下 token 值,客戶端與服務(wù)端值不一致,然后服務(wù)端返回 token 過(guò)期的提示。
值得注意的一點(diǎn)是:
如果借助了?Last-Modified?和?ETag,那么緩存策略則必須使用?NSURLRequestReloadIgnoringCacheData?策略,忽略緩存,每次都要向服務(wù)端進(jìn)行校驗(yàn)。
如果 GET 中包含有版本號(hào)信息
眾多的應(yīng)用都會(huì)在 GET 請(qǐng)求后加上版本號(hào):
http://abc.com?my_current_version=v1.0.0
這種情況下,??v1.0?和??v2.0?兩個(gè)不同版本,請(qǐng)求到的?Last-Modified?和?ETag?會(huì)如預(yù)期嗎?
這完全取決于公司服務(wù)端同事的實(shí)現(xiàn),?Last-Modified?和?ETag?僅僅是一個(gè)協(xié)議,并沒(méi)有統(tǒng)一的實(shí)現(xiàn)方法,而服務(wù)端的處理邏輯完全取決于需求。
你完全可以要求服務(wù)端同事,僅僅判斷資源的異同,而忽略掉??v1.0?和??v2.0?兩個(gè)版本的區(qū)別。
參考鏈接:if-modified-since vs if-none-match
一般數(shù)據(jù)類(lèi)型借助?Last-Modified?與?ETag?進(jìn)行緩存
以上的討論是基于文件資源,那么對(duì)一般的網(wǎng)絡(luò)請(qǐng)求是否也能應(yīng)用?
控制緩存過(guò)期時(shí)間,無(wú)非兩種:設(shè)置一個(gè)過(guò)期時(shí)間;校驗(yàn)緩存與服務(wù)端一致性,只在不一致時(shí)才更新。
一般情況下是不會(huì)對(duì) api 層面做這種校驗(yàn),只在有業(yè)務(wù)需求時(shí)才會(huì)考慮做,比如:
數(shù)據(jù)更新頻率較低,“萬(wàn)不得已不會(huì)更新”---只在服務(wù)器有更新時(shí)才更新,以此來(lái)保證2G 等惡略網(wǎng)絡(luò)環(huán)境下,有較好的體驗(yàn)。比如網(wǎng)易新聞欄目,但相反微博列表、新聞列表就不適合。
業(yè)務(wù)數(shù)據(jù)一致性要求高,數(shù)據(jù)更新后需要服務(wù)端立刻展示給用戶。客戶端顯示的數(shù)據(jù)必須是服務(wù)端最新的數(shù)據(jù)。
有離線展示需求,必須實(shí)現(xiàn)緩存策略,保證弱網(wǎng)情況下的數(shù)據(jù)展示的速度。但不考慮使用緩存過(guò)期時(shí)間來(lái)控制緩存的有效性。
盡量減少數(shù)據(jù)傳輸,節(jié)省用戶流量。
一些建議:
如果是 file 文件類(lèi)型,用?Last-Modified?就夠了。即使?ETag?是首選,但此時(shí)兩者效果一致。九成以上的需求,效果都一致。
如果是一般的數(shù)據(jù)類(lèi)型--基于查詢的 get 請(qǐng)求,比如返回值是 data 或 string 類(lèi)型的 json 返回值。那么?Last-Modified?服務(wù)端支持起來(lái)就會(huì)困難一點(diǎn)。因?yàn)楸热?你做了一個(gè)博客瀏覽 app ,查詢最近的10條博客, 基于此時(shí)的業(yè)務(wù)考慮?Last-Modified?指的是10條中任意一個(gè)博客的更改。那么服務(wù)端需要在你發(fā)出請(qǐng)求后,遍歷下10條數(shù)據(jù),得到“10條中是否至少一個(gè)被修改了”。而且要保證每一條博客表數(shù)據(jù)都有一個(gè)類(lèi)似于記錄?Last-Modified?的字段,這顯然不太現(xiàn)實(shí)。
如果更新頻率較高,比如最近微博列表、最近新聞列表,這些請(qǐng)求就不適合,更多的處理方式是添加一個(gè)接口,客戶端將本地緩存的最后一條數(shù)據(jù)的的時(shí)間戳或 id 傳給服務(wù)端,然后服務(wù)端會(huì)將新增的數(shù)據(jù)條數(shù)返回,沒(méi)有新增則返回 nil 或 304。
參考鏈接:《(慕課網(wǎng))imooc iPhone3.3 接口數(shù)據(jù)緩存》
剩下20%的網(wǎng)絡(luò)緩存需求
真的有NSURLCache?不能滿足的需求?
有人可能要問(wèn):
NSURLCache?不是幫我們做了硬盤(pán)緩存么?那我們?yōu)槭裁匆约河脭?shù)據(jù)庫(kù)做本地緩存啊。為啥不直接用NSURLCache?不是更方便?
系統(tǒng)幫我們做的緩存,好處是自動(dòng),無(wú)需我們進(jìn)行復(fù)雜的設(shè)置。壞處也恰恰是這個(gè):不夠靈活,不能自定義。只能指定一個(gè)緩存的總文件夾,不能分別指定每一個(gè)文件緩存的位置,更不能為每個(gè)文件創(chuàng)建一個(gè)文件夾,也不能指定文件夾的名稱。緩存的對(duì)象也是固定的:只能是 GET請(qǐng)求的返回值。