一. 關(guān)于同一個url的多次請求
有時候熙卡,對同一個URL請求多次掏湾,返回的數(shù)據(jù)可能都是一樣的竹勉,比如服務(wù)器上的某張圖片飞盆,無論下載多少次,返回的數(shù)據(jù)都是一樣的.
上面的情況會造成以下問題
(1) 用戶流量的浪費
(2) 程序響應(yīng)速度不夠快,用戶體驗差
解決上面的問題次乓,一般考慮對數(shù)據(jù)進行緩存.
二. 緩存的理解
為了提高程序的響應(yīng)速度吓歇,可以考慮使用緩存(內(nèi)存緩存\硬盤緩存)
第一次請求數(shù)據(jù)時,內(nèi)存緩存中沒有數(shù)據(jù)票腰,硬盤緩存中沒有數(shù)據(jù),我們只能向服務(wù)器索取.
當(dāng)服務(wù)器返回數(shù)據(jù)時,需要做以下步驟
(1) 使用服務(wù)器的數(shù)據(jù)(比如解析城看、顯示)
(2) 將服務(wù)器的數(shù)據(jù)緩存到硬盤(沙盒)
此時緩存的情況是:內(nèi)存緩存中有數(shù)據(jù),硬盤緩存中有數(shù)據(jù).
再次請求數(shù)據(jù)分為兩種情況:
(1) 如果程序并沒有被關(guān)閉, 一直在運行
那么此時內(nèi)存緩存中有數(shù)據(jù)杏慰,硬盤緩存中有數(shù)據(jù), 如果此時再次請求數(shù)據(jù)测柠,直接使用內(nèi)存緩存中的數(shù)據(jù)即可
(2) 如果程序重新啟動
那么此時內(nèi)存緩存已經(jīng)消失,沒有數(shù)據(jù)缘滥,硬盤緩存依舊存在轰胁,還有數(shù)據(jù),如果此時再次請求數(shù)據(jù)朝扼,需要讀取內(nèi)存中緩存的數(shù)據(jù).
注意:從硬盤緩存中讀取數(shù)據(jù)后赃阀,內(nèi)存緩存中又有數(shù)據(jù)了
三. 緩存---基礎(chǔ)
(1) 簡單說明
- 由于GET請求一般用來查詢數(shù)據(jù),POST請求一般是發(fā)大量數(shù)據(jù)給服務(wù)器處理(變動性比較大),因此一般只對GET請求進行緩存.
- 在ios中,可以使用 NSURLCache 類緩存數(shù)據(jù).
- 在ios5以前,apple只支持內(nèi)存緩存擎颖,在ios5的時候,允許磁盤緩存榛斯,NSURLCache 是根據(jù)NSURLRequest 來實現(xiàn)的,ios6以后,支持http和https(之前只支持http).
(2) NSURLCache
- ios中的緩存技術(shù)使用 NSURLCache 類
- 緩存原理:一個NSURLRequest對應(yīng)一個NSCachedURLResponse
- 緩存技術(shù):把緩存的數(shù)據(jù)都保存到數(shù)據(jù)庫中
(3) NSURLCache的常見用法
//獲得全局緩存對象(沒必要手動創(chuàng)建)
+ (NSURLCache *)sharedURLCache;
//設(shè)置內(nèi)存緩存的最大容量(字節(jié)為單位,默認為512KB)
- (void)setMemoryCapacity:(NSUInteger)memoryCapacity;
//設(shè)置硬盤緩存的最大容量(字節(jié)為單位搂捧,默認為10M)
- (void)setDiskCapacity:(NSUInteger)diskCapacity;
//設(shè)置自定義的NSURLCache作為應(yīng)用緩存管理對象
+ (void)setSharedURLCache:(NSURLCache *)cache;
//設(shè)置內(nèi)存驮俗、磁盤、磁盤路徑
- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(nullable NSString *)path;
//取得某個請求的緩存(取)
- (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
//給請求設(shè)置指定的緩存(存)
- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;
//刪除某個請求對應(yīng)的緩存
- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
//刪除所有緩存
- (void)removeAllCachedResponses;
//按時間段來刪除緩存
- (void)removeCachedResponsesSinceDate:(NSDate *)date NS_AVAILABLE(10_10, 8_0);
硬盤緩存的位置:沙盒/Library/Caches
還有一些屬性:
//內(nèi)存緩存容量大小
@property NSUInteger memoryCapacity;
//磁盤緩存容量大小
@property NSUInteger diskCapacity;
//當(dāng)前已用內(nèi)存容量
@property (readonly) NSUInteger currentMemoryUsage;
//當(dāng)前已用磁盤容量
@property (readonly) NSUInteger currentDiskUsage;
有興趣的話你可以進入NSURLCache這個類里面看看.
(4) 緩存get請求
要想對某個GET請求進行數(shù)據(jù)緩存允跑,非常簡單
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 設(shè)置緩存策略
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
只要設(shè)置了緩存策略王凑,系統(tǒng)會自動利用NSURLCache進行數(shù)據(jù)緩存
(5) iOS對NSURLRequest提供了7種緩存策略:(實際上能用的只有4種)
NSURLRequestUseProtocolCachePolicy // 默認的緩存策略(取決于協(xié)議)
NSURLRequestReloadIgnoringLocalCacheData // 忽略緩存提佣,重新請求
NSURLRequestReloadIgnoringLocalAndRemoteCacheData // 未實現(xiàn)
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData // 忽略緩存,重新請求
NSURLRequestReturnCacheDataElseLoad// 有緩存就用緩存荤崇,沒有緩存就重新請求
NSURLRequestReturnCacheDataDontLoad// 有緩存就用緩存拌屏,沒有緩存就不發(fā)請求,當(dāng)做請求出錯處理(用于離線模式)
NSURLRequestReloadRevalidatingCacheData // 未實現(xiàn)
(6) 緩存的注意事項
緩存的設(shè)置需要根據(jù)具體的情況考慮术荤,如果請求某個URL的返回數(shù)據(jù):
- 經(jīng)常更新:不能用緩存倚喂!比如股票、彩票數(shù)據(jù)
- 一成不變:果斷用緩存
- 偶爾更新:可以定期更改緩存策略 或者 定期清除緩存
提示:如果大量使用緩存瓣戚,會越積越大端圈,建議定期清除緩存
四 你必須了解的
(1) 304狀態(tài)碼:
304是什么意思?為什么要返回304子库?什么時候返回304舱权?
做網(wǎng)絡(luò)緩存經(jīng)常性的在返回304狀態(tài)碼情況下對數(shù)據(jù)進行處理
客戶端發(fā)送了一個帶條件的GET 請求且該請求已被允許,而文檔的內(nèi)容(自上次訪問以來或者根據(jù)請求的條件)并沒有改變仑嗅,則服務(wù)器返回給客戶端的不是請求的數(shù)據(jù)而是狀態(tài)碼304宴倍,意思是告訴你,數(shù)據(jù)沒變化仓技,你可以用之前緩存的數(shù)據(jù)來呈現(xiàn)界面.
例如:一些靜態(tài)頁面鸵贬,如圖片,html脖捻,js等阔逼,這些靜態(tài)頁面往往可能是服務(wù)器早已準(zhǔn)備好的,用戶訪問時僅僅是下載而已. 那么針對這種靜態(tài)頁面地沮,就可以僅僅通過304狀態(tài)碼來判斷嗜浮,內(nèi)容是否發(fā)生了變化. (取自百度百科)
(2) Last-Modified、Etag摩疑、Expires危融、Cache-Control
你想實現(xiàn)網(wǎng)絡(luò)緩存就需要遵循h(huán)ttp的緩存協(xié)議,就需要在服務(wù)端與客戶端進行相應(yīng)的配置未荒,以上字段你可以理解成是四種協(xié)議或者是四種實現(xiàn)緩存的必經(jīng)方法(使用一種或兩種)
1. Last-Modified
在客戶端第一次請求某一個URL時专挪,服務(wù)器端的返回狀態(tài)會是200,內(nèi)容是你請求的資源片排,同時返回給你的還有一個Last-Modified的屬性(在 HttpReponse Header 中)寨腔,這個屬性來標(biāo)記此文件在服務(wù)期端最后被修改的時間,格式類似這樣:
Last-Modified:Tue, 24 Feb 2016 08:01:04 GMT
NSDictionary *dic = [NSDictionary new];
if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *tempResponse = (NSHTTPURLResponse *)task.response;
dic = tempResponse.allHeaderFields;
NSLog(@"allHeaderFields = %@",dic);
}
客戶端第二次請求此URL時率寡,根據(jù)HTTP協(xié)議的規(guī)定迫卢,瀏覽器會向服務(wù)器傳送If-Modified-Since報頭(HttpRequest Header),詢問該時間之后文件是否有被修改過冶共,格式類似這樣:
If-Modified-Since:Tue, 24 Feb 2016 08:01:04 GMT
[[APIClient sharedClient].requestSerializer setValue:@"Tue, 24 Feb 2016 08:01:04 GMT" forHTTPHeaderField:@"If-Modified-Since"];
如果服務(wù)器端的資源沒有變化乾蛤,則自動返回304狀態(tài)碼每界,內(nèi)容為空,這樣就節(jié)省了傳輸數(shù)據(jù)量家卖,當(dāng)服務(wù)器端代碼發(fā)生改變或者重啟服務(wù)器時眨层,則重新發(fā)出資源,返回和第一次請求時類似上荡,從而保證不向客戶端重復(fù)發(fā)出資源趴樱,也保證當(dāng)服務(wù)器有變化時,客戶端能夠得到最新的資源.
注:如果If-Modified-Since的時間比服務(wù)器當(dāng)前時間(當(dāng)前的請求時間request_time)還晚酪捡,會認為是個非法請求
很多時候可能不能僅僅通過標(biāo)記時間來確定當(dāng)前文件有沒有更新叁征,這個時候就需要用到 Etag
2 Etag
Etag 主要為了解決 Last-Modified 無法解決的一些問題。
1逛薇、 一些文件也許會周期性的更改捺疼,但是他的內(nèi)容并不改變(僅僅改變的修改時間),這個時候我們并不希望客戶端認為這個文件被修改了永罚,而重新GET;
2啤呼、某些文件修改非常頻繁,比如在秒以下的時間內(nèi)進行修改尤蛮,(比方說1s內(nèi)修改了N次)媳友,If-Modified-Since能檢查到的粒度是s級的斯议,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒)
3产捞、某些服務(wù)器不能精確的得到文件的最后修改時間;
為此哼御,HTTP/1.1 引入了 Etag(Entity Tags).Etag僅僅是一個和文件相關(guān)的標(biāo)記坯临,可以是一個版本標(biāo)記,比如說v1.0.0或者說"2e681a-6-5d044840"這么一串看起來很神秘的編碼。但是HTTP/1.1標(biāo)準(zhǔn)并沒有規(guī)定Etag的內(nèi)容是什么或者說要怎么實現(xiàn)恋昼,唯一規(guī)定的是Etag需要放在""內(nèi)看靠。
Etag的工作原理與Last-Modified類似,只是它倆標(biāo)記文件的形式不同而已:Etag是以內(nèi)容來計算一個標(biāo)志液肌,計算方式可以使用md5挟炬、SHA1等哈希算法,Last-Modified直接以時間作標(biāo)記.
此處更新了Etag的工作原理(copy嗦哆,之前寫的沒有層次感)
Etag - 工作原理
Etag由服務(wù)器端生成谤祖,客戶端通過If-Match或者說If-None-Match這個條件判斷請求來驗證資源是否修改。常見的是使用If-None-Match.請求一個文件的流程可能如下:
====第一次請求===
1.客戶端發(fā)起 HTTP GET 請求一個文件老速;
2.服務(wù)器處理請求粥喜,返回文件內(nèi)容和一堆Header,Header中包括Etag(Etag:“5d8c72a5edda8d6a:3239″)(假設(shè)服務(wù)器支持Etag生成和已經(jīng)開啟了Etag).狀態(tài)碼200,(客戶端此時應(yīng)該把Etag值保存在本地)
網(wǎng)絡(luò)圖片:
====第二次請求===
1.客戶端發(fā)起 HTTP GET 請求一個文件橘券,注意這個時候客戶端同時發(fā)送一個If-None-Match頭额湘,這個頭的內(nèi)容就是第一次請求時服務(wù)器返回的Etag:“5d8c72a5edda8d6a:3239″(如下所示)
[[APIClient sharedClient].requestSerializer setValue:@"5d8c72a5edda8d6a:3239" forHTTPHeaderField:@"If-None-Match"];
2.服務(wù)器判斷發(fā)送過來的Etag和計算出來的Etag匹配卿吐,如果If-None-Match為Yes,說明文件內(nèi)容更改了,返回200(當(dāng)然還有新的包含在頭文件中的Etag)锋华,客戶端保存(更新)此Etag以便下次發(fā)送這個請求的時候使用,如果If-None-Match為False嗡官,返回304,客戶端繼續(xù)使用本地緩存毯焕;
流程很簡單谨湘,問題是,如果服務(wù)器又設(shè)置了Cache-Control:max-age和Expires呢芥丧,怎么辦紧阔?
答案是同時使用,也就是說在完全匹配If-Modified-Since和If-None-Match即檢查完修改時間和Etag之后续担,服務(wù)器才能返回304.
3 Expires
給出的日期/時間之后的時間擅耽,就被響應(yīng)認為是過時。如Expires:Thu, 02 Apr 2009 05:14:08 GMT
需和Last-Modified結(jié)合使用物遇。用于控制請求文件的有效時間乖仇,當(dāng)請求數(shù)據(jù)在有效期內(nèi)時客戶端瀏覽器從緩存請求數(shù)據(jù)而不是服務(wù)器端.當(dāng)緩存中數(shù)據(jù)失效或過期,才決定從服務(wù)器更新數(shù)據(jù)询兴。
4 Cache-Control
(1) Cache-control: max-age=5
表示當(dāng)訪問此網(wǎng)頁后的5秒內(nèi)再次訪問不會去服務(wù)器 .
(2) Cache-Control: no-cache:這個很容易讓人產(chǎn)生誤解乃沙,使人誤以為是響應(yīng)不被緩存。實際上Cache-Control: no-cache是會被緩存的诗舰,只不過每次在向客戶端(瀏覽器)提供響應(yīng)數(shù)據(jù)時警儒,緩存都要向服務(wù)器評估緩存響應(yīng)的有效性.
(3) Cache-Control: no-store:這個才是響應(yīng)不被緩存的意思.
下圖是使用cache-control打印出來的 httpResponse.allHeaderFields 效果圖
五 客戶端使用NSURLCache配合AFNetworking進行網(wǎng)絡(luò)緩存
1 為了更方便的使用 NSURLCache,你需要初始化并設(shè)置一個共享(全局)的URL緩存眶根,在-application:didFinishLaunchingWithOptions:中完成設(shè)置
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURLCache *URLCache = [[NSURLCache alloc]initWithMemoryCapacity:5 * 1024 *1024 diskCapacity:30 * 1024 *1024 diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
return YES;
}
這里我們設(shè)置了一個內(nèi)存5M蜀铲、磁盤30M的緩存!
緩存策略由請求(客戶端)和回應(yīng)(服務(wù)端)分別指定, 理解這些策略以及它們?nèi)绾蜗嗷ビ绊懯舭伲菫槟膽?yīng)用程序找到最佳行為的關(guān)鍵.
2 我們使用Etag演示url緩存
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:(NSURLRequestReloadIgnoringCacheData) timeoutInterval:15.0];
//發(fā)送etag
NSString *etag = [[NSUserDefaults standardUserDefaults]objectForKey:@"url對應(yīng)的key值"];
if (etag.length > 0) {
[mutableRequest setValue:etag forHTTPHeaderField:@"If-None-Match"];
}
[[NSURLSession sharedSession]dataTaskWithRequest:mutableRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(@"statusCode == %ld", httpResponse.statusCode);
if (httpResponse.statusCode == 304) {// 如果是304记劝,使用本地緩存
// 根據(jù)請求獲取到`被緩存的響應(yīng)`!
NSCachedURLResponse *cacheResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:mutableRequest];
// 拿到緩存的數(shù)據(jù),然后進行頁面顯示
id data = cacheResponse.data;
}else{//假設(shè)請求成功(200)
//獲取并存儲etag
NSString *etag = httpResponse.allHeaderFields[@"Etag"];
[[NSUserDefaults standardUserDefaults]setObject:etag forKey:@"url對應(yīng)的key值"];
//顯示數(shù)據(jù)
}
}];
Last-Modified與Etag基本上相同族扰,這里就不做演示了厌丑,事實上我們在使用Last-Modified的時候多半都伴隨著Expires的使用.
這里還有一個疑問,我們的數(shù)據(jù)緩存在哪里?
(App Sandbox)/Library/Caches/bundleId+項目名/下面有三個文件渔呵,以SQLite數(shù)據(jù)庫文件的形式存放
3 定期處理緩存
// 定期處理緩存
if (緩存有效或者沒到指定日期) {
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
}
// 獲得全局的緩存對象
NSURLCache *cache = [NSURLCache sharedURLCache];
if (緩存無效或者超時) {
//刪除此request對應(yīng)的緩存文件
[cache removeCachedResponseForRequest:request];
}
NSCachedURLResponse *response = [cache cachedResponseForRequest:request];
if (response) {
NSLog(@"---這個請求已經(jīng)存在緩存");
} else {
NSLog(@"---這個請求沒有緩存");
}