來聊聊ios下的url緩存問題

一. 關(guān)于同一個url的多次請求

有時候熙卡,對同一個URL請求多次掏湾,返回的數(shù)據(jù)可能都是一樣的竹勉,比如服務(wù)器上的某張圖片飞盆,無論下載多少次,返回的數(shù)據(jù)都是一樣的.


012208303914500.png

上面的情況會造成以下問題

(1) 用戶流量的浪費
(2) 程序響應(yīng)速度不夠快,用戶體驗差
解決上面的問題次乓,一般考慮對數(shù)據(jù)進行緩存.

二. 緩存的理解

為了提高程序的響應(yīng)速度吓歇,可以考慮使用緩存(內(nèi)存緩存\硬盤緩存)

012210436882045.png

第一次請求數(shù)據(jù)時,內(nèi)存緩存中沒有數(shù)據(jù)票腰,硬盤緩存中沒有數(shù)據(jù),我們只能向服務(wù)器索取.
當(dāng)服務(wù)器返回數(shù)據(jù)時,需要做以下步驟
(1) 使用服務(wù)器的數(shù)據(jù)(比如解析城看、顯示)
(2) 將服務(wù)器的數(shù)據(jù)緩存到硬盤(沙盒)

012211454225683.png

此時緩存的情況是:內(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ò)圖片:


etag.png

====第二次請求===
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 效果圖

1626334K2-2.png

五 客戶端使用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ù)庫文件的形式存放

162633H21-1.png

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(@"---這個請求沒有緩存");
 }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怒竿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子厘肮,更是在濱河造成了極大的恐慌愧口,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件类茂,死亡現(xiàn)場離奇詭異耍属,居然都是意外死亡托嚣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門厚骗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來示启,“玉大人,你說我怎么就攤上這事领舰》蛏ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵冲秽,是天一觀的道長舍咖。 經(jīng)常有香客問我,道長锉桑,這世上最難降的妖魔是什么排霉? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮民轴,結(jié)果婚禮上攻柠,老公的妹妹穿的比我還像新娘。我一直安慰自己后裸,他們只是感情好瑰钮,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著微驶,像睡著了一般浪谴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上祈搜,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天较店,我揣著相機與錄音,去河邊找鬼容燕。 笑死,一個胖子當(dāng)著我的面吹牛婚度,可吹牛的內(nèi)容都是我干的蘸秘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蝗茁,長吁一口氣:“原來是場噩夢啊……” “哼醋虏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哮翘,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颈嚼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饭寺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阻课,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡叫挟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了限煞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抹恳。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖署驻,靈堂內(nèi)的尸體忽然破棺而出奋献,到底是詐尸還是另有隱情,我是刑警寧澤旺上,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布瓶蚂,位于F島的核電站,受9級特大地震影響宣吱,放射性物質(zhì)發(fā)生泄漏扬跋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一凌节、第九天 我趴在偏房一處隱蔽的房頂上張望钦听。 院中可真熱鬧,春花似錦倍奢、人聲如沸朴上。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痪宰。三九已至,卻和暖如春畔裕,著一層夾襖步出監(jiān)牢的瞬間衣撬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工扮饶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留具练,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓甜无,卻偏偏與公主長得像扛点,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子岂丘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容