1. 程式人生 > >iOS Http網路請求、快取、及網路資料更新才請求

iOS Http網路請求、快取、及網路資料更新才請求

iOS網路快取掃盲篇

--使用兩行程式碼就能完成80%的快取需求

目錄

由於微信、QQ、微博、這類的應用使用快取很“重”,使一般的使用者也對快取也非常習慣。快取已然成為必備。

快取的目的的以空間換時間

這句話在動輒就是 300M、600M 的大應用上,得到了很好的詮釋。但能有快取意識的公司,還在少數。

只有你真正感受到痛的時候,你才會考慮使用快取。

這個痛可能是:

伺服器壓力、客戶端網路優化、使用者體驗等等。

當我們在談論快取的時候,我們在談論什麼?

我們今天將站在小白使用者的角度,給快取這個概念進行重新的定義。

快取有不同的分類方法:

enter image description here

這裡所指的快取,是一個寬泛的概念。

我們這裡主要按照功能進行劃分:

enter image description here

- 第一種 第二種
目的 優化型快取 功能型快取
具體描述 出於優化考慮:伺服器壓力、使用者體驗、為使用者剩流量等等。同時優化型快取也有記憶體快取和磁碟快取之分。 App離線也能檢視,出於功能考慮,屬於儲存範疇
常見概念 GET網路請求快取、WEB快取 離線儲存
典型應用 微信首頁的會話列表、微信頭像、朋友圈、網易新聞新聞列表、 微信聊天記錄、
Parse對應的類 PFCachedQueryController PFOfflineStore

重度使用快取的App: 微信、微博、網易新聞、攜程、去哪兒等等。

GET網路請求快取

概述

首先要知道,POST請求不能被快取,只有 GET 請求能被快取。因為從數學的角度來講,GET 的結果是 冪等

 的,就好像字典裡的 key 與 value 就是冪等的,而 POST 不 冪等 。快取的思路就是將查詢的引數組成的值作為 key ,對應結果作為value。從這個意義上說,一個檔案的資源連結,也叫 GET 請求,下文也會這樣看待。

80%的快取需求:兩行程式碼就可滿足

設定快取只需要三個步驟:

第一個步驟:請使用 GET 請求。

第二個步驟:

如果你已經使用 了 GET 請求,iOS 系統 SDK 已經幫你做好了快取。你需要的僅僅是設定下記憶體快取大小、磁碟快取大小、以及快取路徑。甚至這兩行程式碼不設定也是可以的,會有一個預設值。程式碼如下:

NSURLCache *urlCache = [[NSURLCache
alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil]; [NSURLCache setSharedURLCache:urlCache];

第三個步驟:沒有第三步!

你只要設定了這兩行程式碼,基本就可滿足80%的快取需求。AFNetworking 的作者 Mattt曾經說過:

無數開發者嘗試自己做一個簡陋而脆弱的系統來實現網路快取的功能,殊不知 NSURLCache 只要兩行程式碼就能搞定且好上 100 倍。

(AFN 是不是在暗諷 SDWebImage 複雜又蹩腳的快取機制??)

要注意

  • iOS 5.0開始,支援磁碟快取,但僅支援 HTTP
  • iOS 6.0開始,支援 HTTPS 快取

控制快取的有效性

我們知道:

  • 只要是快取,總會過期。

那麼快取的過期時間如何控制?

上文中的兩行程式碼,已經給出了一個方法,指定超時時間。但這並也許不能滿足我們的需求,如果我們對資料的一致性,時效性要求很高,即使1秒鐘後資料更改了,客戶端也必須展示更改後的資料。這種情況如何處理?

下面我們將對這種需求,進行解決方案的介紹。順序是這樣的:先從檔案型別的快取入手,引入兩個概念。然後再談下,一般資料型別比如 JSON 返回值的快取處理。

檔案快取:藉助ETag或Last-Modified判斷檔案快取是否有效

Last-Modified

伺服器的檔案存貯,大多采用資源變動後就重新生成一個連結的做法。而且如果你的檔案儲存採用的是第三方的服務,比如七牛、青雲等服務,則一定是如此。

這種做法雖然是推薦做法,但同時也不排除不同檔案使用同一個連結。那麼如果服務端的file更改了,本地已經有了快取。如何更新快取?

這種情況下需要藉助 ETag 或 Last-Modified 判斷圖片快取是否有效。

Last-Modified 顧名思義,是資源最後修改的時間戳,往往與快取時間進行對比來判斷快取是否過期。

在瀏覽器第一次請求某一個URL時,伺服器端的返回狀態會是200,內容是你請求的資源,同時有一個Last-Modified的屬性標記此檔案在服務期端最後被修改的時間,格式類似這樣:

        Last-Modified: Fri, 12 May 2006 18:53:33 GMT

客戶端第二次請求此URL時,根據 HTTP 協議的規定,瀏覽器會向伺服器傳送 If-Modified-Since 報頭,詢問該時間之後檔案是否有被修改過:

        If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT

總結下來它的結構如下:

請求 HeaderValue 響應 HeaderValue
Last-Modified If-Modified-Since

如果伺服器端的資源沒有變化,則自動返回 HTTP 304 (Not Changed.)狀態碼,內容為空,這樣就節省了傳輸資料量。當伺服器端程式碼發生改變或者重啟伺服器時,則重新發出資源,返回和第一次請求時類似。從而保證不向客戶端重複發出資源,也保證當伺服器有變化時,客戶端能夠得到最新的資源。

判斷方法用偽程式碼表示:

if ETagFromServer != ETagOnClient || LastModifiedFromServer != LastModifiedOnClient
   GetFromServer
else
   GetFromCache

之所以使用

LastModifiedFromServer != LastModifiedOnClient

而非使用:

LastModifiedFromServer > LastModifiedOnClient

原因是考慮到可能出現類似下面的情況:服務端可能對資原始檔,廢除其新版,回滾啟用舊版本,此時的情況是:

LastModifiedFromServer <= LastModifiedOnClient

但我們依然要更新本地快取。

Demo10和 Demo11 給出了一個完整的校驗步驟:

並給出了 NSURLConnection 和 NSURLSession 兩個版本:

/*!
 @brief 如果本地快取資源為最新,則使用使用本地快取。如果伺服器已經更新或本地無快取則從伺服器請求資源。

 @details

 步驟:
 1. 請求是可變的,快取策略要每次都從伺服器載入
 2. 每次得到響應後,需要記錄住 LastModified
 3. 下次傳送請求的同時,將LastModified一起傳送給伺服器(由伺服器比較內容是否發生變化)

 @return 圖片資源
 */
- (void)getData:(GetDataCompletion)completion {
    NSURL *url = [NSURL URLWithString:kLastModifiedImageURL];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];

    //    // 傳送 etag
    //    if (self.etag.length > 0) {
    //        [request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
    //    }
    // 傳送 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);
        // 型別轉換(如果將父類設定給子類,需要強制轉換)
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSLog(@"statusCode == %@", @(httpResponse.statusCode));
        // 判斷響應的狀態碼是否是 304 Not Modified (更多狀態碼含義解釋: https://github.com/ChenYilong/iOSDevelopmentTips)
        if (httpResponse.statusCode == 304) {
            NSLog(@"載入本地快取圖片");
            // 如果是,使用本地快取
            // 根據請求獲取到`被快取的響應`!
            NSCachedURLResponse *cacheResponse =  [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
            // 拿到快取的資料
            data = cacheResponse.data;
        }

        // 獲取並且紀錄 etag,區分大小寫
        //        self.etag = httpResponse.allHeaderFields[@"Etag"];
        // 獲取並且紀錄 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 協議規格說明定義ETag為“被請求變數的實體值” (參見 —— 章節 14.19)。 另一種說法是,ETag是一個可以與Web資源關聯的記號(token)。它是一個 hash 值,用作 Request 快取請求頭,每一個資原始檔都對應一個唯一的  ETag 值, 伺服器單獨負責判斷記號是什麼及其含義,並在HTTP響應頭中將其傳送到客戶端,以下是伺服器端返回的格式:

    ETag: "50b1c1d4f775c61:df3"


    客戶端的查詢更新格式是這樣的:

    If-None-Match: W/"50b1c1d4f775c61:df3"

其中:

  • If-None-Match - 與響應頭的 Etag 相對應,可以判斷本地快取資料是否發生變化

    如果ETag沒改變,則返回狀態304然後不返回,這也和Last-Modified一樣。
    

總結下來它的結構如下:

請求 HeaderValue 響應 HeaderValue
ETag If-None-Match

ETag 是的功能與 Last-Modified 類似:服務端不會每次都會返回檔案資源。客戶端每次向服務端傳送上次伺服器返回的 ETag 值,伺服器會根據客戶端與服務端的  ETag 值是否相等,來決定是否返回 data,同時總是返回對應的 HTTP 狀態碼。客戶端通過 HTTP 狀態碼來決定是否使用快取。比如:服務端與客戶端的 ETag 值相等,則 HTTP 狀態碼為 304,不返回 data。服務端檔案一旦修改,服務端與客戶端的 ETag 值不等,並且狀態值會變為200,同時返回 data。

因為修改資原始檔後該值會立即變更。這也決定了 ETag 在斷點下載時非常有用。 比如 AFNetworking 在進行斷點下載時,就是藉助它來檢驗資料的。詳見在  AFHTTPRequestOperation 類中的用法:

    //下載暫停時提供斷點續傳功能,修改請求的HTTP頭,記錄當前下載的檔案位置,下次可以從這個位置開始下載。
- (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"]) {
    //若請求返回的頭部有ETag,則續傳時要帶上這個ETag,
    //ETag用於放置檔案的唯一標識,比如檔案MD5值
    //續傳時帶上ETag服務端可以校驗相對上次請求,檔案有沒有變化,
    //若有變化則返回200,迴應新檔案的全資料,若無變化則返回206續傳。
        [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"];
    }
    //給當前request加Range頭部,下次請求帶上頭部,可以從offset位置繼續下載
    [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"];
    self.request = mutableURLRequest;

    [super pause];
}

七牛等第三方檔案儲存商現在都已經支援ETag,Demo8和9 中給出的演示圖片就是使用的七牛的服務,見:

static NSString *const kETagImageURL = @"http://ac-g3rossf7.clouddn.com/xc8hxXBbXexA8LpZEHbPQVB.jpg";

下面使用一個 Demo 來進行演示用法,

以 NSURLConnection 搭配  ETag 為例,步驟如下:

  • 請求的快取策略使用 NSURLRequestReloadIgnoringCacheData,忽略本地快取
  • 伺服器響應結束後,要記錄 Etag,伺服器內容和本地快取對比是否變化的重要依據
  • 在傳送請求時,設定 If-None-Match,並且傳入 Etag
  • 連線結束後,要判斷響應頭的狀態碼,如果是 304,說明本地快取內容沒有發生變化

以下程式碼詳見 Demo08 :

/*!
 @brief 如果本地快取資源為最新,則使用使用本地快取。如果伺服器已經更新或本地無快取則從伺服器請求資源。

 @details

 步驟:
 1. 請求是可變的,快取策略要每次都從伺服器載入
 2. 每次得到響應後,需要記錄住 etag
 3. 下次傳送請求的同時,將etag一起傳送給伺服器(由伺服器比較內容是否發生變化)

 @return 圖片資源
 */
- (void)getData:(GetDataCompletion)completion {
    NSURL *url = [NSURL URLWithString:kETagImageURL];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];

    // 傳送 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
        // 型別轉換(如果將父類設定給子類,需要強制轉換)
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSLog(@"statusCode == %@", @(httpResponse.statusCode));
        // 判斷響應的狀態碼是否是 304 Not Modified (更多狀態碼含義解釋: https://github.com/ChenYilong/iOSDevelopmentTips)
        if (httpResponse.statusCode == 304) {
            NSLog(@"載入本地快取圖片");
            // 如果是,使用本地快取
            // 根據請求獲取到`被快取的響應`!
            NSCachedURLResponse *cacheResponse =  [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
            // 拿到快取的資料
            data = cacheResponse.data;
        }

        // 獲取並且紀錄 etag,區分大小寫
        self.etag = httpResponse.allHeaderFields[@"Etag"];

        NSLog(@"etag值%@", self.etag);
        !completion ?: completion(data);
    }];
}

相應的  NSURLSession 搭配 ETag 的版本見 Demo09:

/*!
 @brief 如果本地快取資源為最新,則使用使用本地快取。如果伺服器已經更新或本地無快取則從伺服器請求資源。

 @details

 步驟:
 1. 請求是可變的,快取策略要每次都從伺服器載入
 2. 每次得到響應後,需要記錄住 etag
 3. 下次傳送請求的同時,將etag一起傳送給伺服器(由伺服器比較內容是否發生變化)

 @return 圖片資源
 */
- (void)getData:(GetDataCompletion)completion {
    NSURL *url = [NSURL URLWithString:kETagImageURL];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];

    // 傳送 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);
        // 型別轉換(如果將父類設定給子類,需要強制轉換)
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSLog(@"statusCode == %@", @(httpResponse.statusCode));
        // 判斷響應的狀態碼是否是 304 Not Modified (更多狀態碼含義解釋: https://github.com/ChenYilong/iOSDevelopmentTips)
        if (httpResponse.statusCode == 304) {
            NSLog(@"載入本地快取圖片");
            // 如果是,使用本地快取
            // 根據請求獲取到`被快取的響應`!
            NSCachedURLResponse *cacheResponse =  [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
            // 拿到快取的資料
            data = cacheResponse.data;
        }

        // 獲取並且紀錄 etag,區分大小寫
        self.etag = httpResponse.allHeaderFields[@"Etag"];

        NSLog(@"%@", self.etag);
        dispatch_async(dispatch_get_main_queue(), ^{
            !completion ?: completion(data);
        });
    }] resume];
}

執行效果:

enter image description here

總結

在官方給出的文件中提出 ETag 是首選的方式,優於 Last-Modified 方式。因為 ETag 是基於 hash ,hash 的規則可以自己設定,而且是基於一致性,是“強校驗”。 Last-Modified 是基於時間,是弱校驗,弱在哪裡?比如說:如果服務端的資源回滾客戶端的 Last-Modified 反而會比服務端還要新。

雖然 ETag 優於 Last-Modified ,但並非所有服務端都會支援,而 Last-Modified 則一般都會有該欄位。 大多數情況下需要與服務端進行協調支援 ETag ,如果協商無果就只能退而求其次。

Demo 也給出了一個不支援 ETag 的連結,基本隨便找一張圖片都行:

static NSString *const kLastModifiedImageURL = @"http://image17-c.poco.cn/mypoco/myphoto/20151211/16/17338872420151211164742047.png";

相關推薦

iOS Http網路請求快取網路資料更新請求

iOS網路快取掃盲篇 --使用兩行程式碼就能完成80%的快取需求 目錄 由於微信、QQ、微博、這類的應用使用快取很“重”,使一般的使用者也對快取也非常習慣。快取已然成為必備。 快取的目的的以空間換時間 這句話在動輒就是 300M、600M 的大應用上,得到了

快取擊穿快取失效熱點key的解決方案

分散式快取是網站服務端經常用到的一種技術,在讀多寫少的業務場景中,通過使用快取可以有效地支撐高併發的訪問量,對後端的資料庫等資料來源做到很好地保護。現在市面上有很多分散式快取,比如Redis、Memcached以及阿里的Tair等,不管我們使用的哪種快取產品,基本上都會遇到快取擊穿、快取失效以及熱點key的問

公有鏈聯盟鏈私有鏈網路配置介紹

以太坊網路 去中心化共識的基礎是參與節點的點對點網路,節點維護並保證區塊鏈網路的安全。參見挖礦。 以太坊網路資料統計 EthStats.net是以太坊網路實時資料的儀表板,這個儀表板展示重要資訊,諸如現在的區塊,散表難度,gas價格和gas花費等。頁面上顯示的節點只

基於OkHttp Retrofit RxJava 多執行緒下載。請求快取自動更新.限制佇列數.封裝庫

XDownload介紹 本庫封裝基於Okhttp3,Retrofit2,RxJava2.0,Greendao3.2 ps : 當然當然,都封裝好了,你也可以無視 GitHub地址 如果你覺得好用,對你有幫助,請給個star 介面

區塊鏈開發(十二)公有鏈聯盟鏈私有鏈網路配置介紹

以太坊網路 去中心化共識的基礎是參與節點的點對點網路,節點維護並保證區塊鏈網路的安全。參見挖礦。 以太坊網路資料統計 EthStats.net是以太坊網路實時資料的儀表板,這個儀表板展示重要資訊,諸如現在的區塊,散表難度,gas價格和gas花費等。

延遲載入快取spring與宣告式事務

什麼是延遲載入 延遲載入又稱(懶載入) resultMap中的 association 和 collection 標籤就具有延遲載入的功能(一對一,一對多的關係自帶延遲載入,在開發裡面最常用的) - 作用是:什麼時候用什麼時候載入 設定延遲載入 <!

Nginx反向代理負載均衡動靜分離快取壓縮防盜鏈跨域訪問

一、反向代理 1、在192.168.189.130機器啟動tomcat服務,http://192.168.189.130:8080/ 訪問服務正常 2、在192.168.189.131機器配置nginx server { listen 80; serve

分散式鎖的3種實現(資料庫快取Zookeeper)

分散式鎖的幾種實現方式 目前幾乎很多大型網站及應用都是分散式部署的,分散式場景中的資料一致性問題一直是一個比較重要的話題。 分散式的CAP理論告訴我們,任何一個分散式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分割槽容錯性(

Nginx配置之負載均衡限流快取黑名單和灰度釋出

一、Nginx安裝(基於CentOS 6.5) 1.yum命令安裝 yum install nginx –y (若不能安裝,執行命令yum install epel-release) 2. 啟動、停止和重啟 service nginx start service nginx stop

Asp.Net MVC3 簡單入門詳解過濾器Filter(身份驗證快取防盜鏈國際化等)

下面我們說幾個系統的Filter三、AcceptVerbs規定頁面的訪問形式,如 [AcceptVerbs(HttpVerbs.Post)]public ActionResult Example(){return View(); }頁面只能以Post形式訪問,即表單提交。四、Ac

js的動態載入快取更新以及複用(三)

總體思路 1、  建立一個js服務,該服務實現通用js檔案的載入、依賴、快取、更新以及複用。 2、  各個專案如果使用通用js,可(bi)以(xu)使用js服務實現載入。 3、  Js服務只提供通用的js,比如jQuery、my97、easyUI等(可根據實際情況設定具體的js檔案)。 4、  其他針

js的動態載入快取更新以及複用(四)

  本來想一氣呵成,把載入的過程都寫了,但是卡著呢,所以只好在分成兩份了。   1、頁面裡使用<script>來載入 boot.js 。   2、然後在boot.js裡面動態載入 bootLoad.js。以時間作為標識 var dateVer = date.getYear() + '_

js的動態載入快取更新以及複用(一)

使用範圍:   OA、MIS、ERP等資訊管理類的專案,暫時不考慮網站。 遇到的問題:   完成一個專案,往往需要引用很多js檔案,比如jQuery.js、easyUI等。還有自己寫的一些列js檔案,那麼這些檔案如何方便的載入,如果檔案有變化如何才能讓客戶端及時更新快取?如果能夠提高點執行效率,

js的動態載入快取更新以及複用(二)

  上一篇發出來後得到了很多回復,在此首先感謝大家的熱情捧場!有的推薦第三方框架,比如 In.js、requrieJS、sea.js、lab.js等。這個開闊了眼界,以前只知道sea.js,省去了自己搜尋的麻煩。也用了點時間簡單看了一下,因為每一個都是大塊頭,都有自己的理念,如果只是簡單使用的話,那麼誰便

暫存器快取記憶體硬碟儲存器的理解

只要能儲存資料的器件都可以稱之為儲存器,它的含義覆蓋了暫存器,快取,記憶體,硬碟。cpu訪問快慢的速度依次為 暫存器-> 快取->記憶體->硬碟 暫存器是中央處理器的組成部分,是一種

Nginx配之負載均衡快取黑名單和灰度釋出

一、Nginx安裝(基於CentOS 6.5) 1.yum命令安裝 yum install nginx –y (若不能安裝,執行命令yum install epel-release) 2. 啟動、停止和重啟 service nginx start service nginx

Laravel隱式控制器快取常用操作

隱式控制器 路由宣告: Route::controller('users', 'UsController'); 1 控制器寫法 class UsController extends Controller { //get請求預設方法 //位址列請求:ht

一種高效可自動擴容快取永久儲存通用方案設計

離線訊息儲存方案 1 系統框架設計 1.1 名詞解釋: 名詞 解釋 備註 OffServer叢集 離線訊息伺服器叢集 Redis 一個redis例項 用於儲存一些關鍵資訊 MysqlKeyHashServer(MKHS) 代理整

Picasso原始碼分析(二):預設的下載器快取執行緒池和轉換器

下載器 當用戶沒有為Picasso指定下載器的時候Picasso會通過Utils.createDefaultDownloader(context)方法建立一個預設的下載器 static Downloader createDefaultDownlo

除了負載均衡,Nginx還可以做很多,限流快取黑白名單等

Nginx應該是現在最火的web和反向代理伺服器,沒有之一。 她是一款誕生於俄羅斯的高效能web伺服器,尤其在高併發情況下,相較