[iOS] iOS中URLRequest的緩存策略

Example 項目地址: github.com/zColdWater/… 下載Demo,結合Demo一塊兒實踐更容易理解。html

一,準備工做

在開始以前,咱們須要清楚下面的一些問題,才方便咱們後面的講解。git

1. URLRequest 涉及的範圍

咱們一提到URLRequest,我相信不少國內的開發者,首先就會聯想到,HTTP請求,而後木有別的了。 可是其實 URLRequest是一個很大的概念,它不僅服務於HTTP協議,它還服務於 其餘應用協議,好比File協議,Data協議,自定義協議等等。 要麼蘋果公司爲何不叫它HTTPURLRequest呢? 問題就在於咱們平時最常接觸的就是HTTP協議,用來請求服務端的數據用來展現。github

2. URLRequest.CachePolicy 涉及的範圍

經過上面的文章咱們清楚了 URLRequest 服務不少協議,那麼URLRequest.CachePolicy的範圍是什麼呢,很明顯,和URLRequest同樣,這個緩存策略也包含上面這些協議,固然 我也不清楚其餘協議的緩存策略是什麼樣子的,好比File協議,或則別的。 可是我很清楚,咱們經常使用的HTTP協議的緩存協議,這個後面再講,這裏清楚的是,這個緩存策略支持不少協議,咱們的HTTP協議有着本身的緩存策略。web

3. HTTP協議的緩存策略

我以前轉了一篇臺灣做者關於HTTP協議的緩存策略的文章,文章地址是: http://47.99.237.180:8080/articles/2019/11/18/1574050998351.html 那麼HTTP協議的緩存策略是什麼呢?swift

Note: 首先咱們須要清楚的是,HTTP協議的策略是須要客戶端服務端配合完成的。也就是若是這個策略想要完成,須要雙方都有動做,而且客戶端須要徹底配合才行。瀏覽器

我用最直白的話來概述這個原理:緩存

  • 首先客戶端第一次訪問服務器某一個資源,而且服務器客戶端協商好,咱們都用標準的HTTP緩存協議。bash

  • 由於是第一次,客戶端經過URL來查找,發現本地沒有緩存,直接向服務器發起一個HTTP協議的網絡請求。 客戶端的請求頭,例如:服務器

    GET /EC6/poster-share-invite.png HTTP/1.1
    Host: fep-sit.nioint.com:5418
    Accept: */*
    User-Agent: SessionDownload/1 CFNetwork/1107.1 Darwin/19.0.0
    Accept-Language: en-us
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    複製代碼

    這其實就是一個普通的網絡請求,請求頭也是客戶端默認的,沒有特殊設置。網絡

  • 服務器接到了某一個客戶端的發來的請求,而後作的直接就看一下這個請求頭有沒有提供HTTP協議緩存的相關字段,而後根據HTTP協議緩存規則,來判斷是否返回狀態碼304讓客戶端用緩存,仍是返回狀態碼200,讓客戶端使用服務器返回的新資源,服務器檢查請求頭的相關字段以下

    • If-None-Match: W/"20f9a-16f5c76370d" 這個字段你能夠理解爲這個資源的惟一Hash值,有點像MD5或者SHA1等,反正就是一個惟一標識啦,資源若是有變更,它必定就會有變更,而且這個值是從上一次服務器返回的響應頭裏面的Etag字段取來的。由於咱們客戶端是第一次請求,因此沒有從以前的服務器響應裏面拿到這個值,因此請求頭就沒有這個字段。
    • If-Modified-Since:Tue, 31 Dec 2019 14:57:28 GMT,這個字段表示的是最後一次資源更改的時間,同If-None-Match也是從上一次的服務器響應頭中拿到,從Last-Modified字段取的。由於第一次請求,因此沒有獲取到上一次響應頭的字段,也就沒有帶上。
  • 服務器開始根據HTTP協議規則進行檢查,來決定是讓客戶端使用緩存仍是使用服務器下發的資源。

    • 服務器的兩種響應頭:
      1. 狀態碼200(告訴客戶端,不要使用緩存,用我給你的新資源)
        HTTP/1.1 200 OK  
         X-Powered-By: Express  
         Accept-Ranges: bytes  
         Cache-Control: public, max-age=0  
         Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT  
         ETag: W/"20f9a-16f5c76370d"  
         Content-Type: image/gif  
         Content-Length: 135066  
         Date: Wed, 01 Jan 2020 01:56:35 GMT  
         Proxy-Connection: keep-alive
        複製代碼
      2. 狀態碼304(告訴客戶端你用本身的緩存便可)

        這裏注意的是,在iOS當中,你不須要親自處理304的狀況,若是你使用了默認的緩存策略,也就是使用HTTP協議自己的緩存策略,系統的網絡框架好比URLSession或者URLConnection會自動的將這個304處理成200,這樣方便了開發者邏輯處理,開發者只須要知道 資源獲取成功,就能夠了。

        HTTP/1.1 304 Not Modified
        X-Powered-By: Express
        Accept-Ranges: bytes
        Cache-Control: public, max-age=0
        Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
        ETag: W/"20f9a-16f5c76370d"
        Date: Wed, 01 Jan 2020 01:59:25 GMT
        Proxy-Connection: keep-alive
        複製代碼
    • 服務器響應頭裏與HTTP協議緩存策略相關的字段:
      1. Cache-Control: max-age = X (max-age=x 是告訴客戶端x秒以內不要再發起請求了,就用你的緩存就OK了,換句話說,若是服務器告訴客戶端max-age=100,客戶端在100s以內再去請求,是不會發起真正的網絡請求的,客戶端的網絡層框架會自動返回狀態碼200,上一次的緩存數據)
      2. Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT(這個字段是告訴客戶端,這個資源最後一次更新的時間,讓客戶端保存好,下一次請求的時候,在請求頭裏面帶上這個值,請求頭的那個字段就是If-Modified-Since,這裏的規則是這樣的,若是請求頭裏的If-Modified-Since時間點早於服務器的Last-Modified時間點,服務器會返回200,讓客戶端須要更新最新資源,若是反過來,或者相同,服務器會下發304,讓客戶端使用緩存。)
      3. ETag: W/"20f9a-16f5c76370d"(這個字段告訴客戶端,這個值是這個資源的惟一id,若是服務器上面有新資源,咱們會更新這個值,客戶端要保存好,下次請求的時候帶上,下次請求頭裏面這個If-None-Match字段就是保存的上次響應頭裏面的Etag字段。它的規則是,若是客戶端請求頭中的If-None-Match值不與服務器裏面的Etag一致,就返回200,讓客戶端使用新資源,只有當相等的狀況,會返回304,讓客戶端使用緩存。)
  • 服務器檢查完請求頭髮現這個客戶端沒有帶上資源緩存信息,那麼服務器就認爲客戶端不想使用HTTP協議緩存策略,返回200,把資源也一同返回。

    HTTP/1.1 200 OK
    X-Powered-By: Express
    Accept-Ranges: bytes
    Cache-Control: public, max-age=0
    Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
    ETag: W/"20f9a-16f5c76370d"
    Content-Type: image/gif
    Content-Length: 135066
    Date: Wed, 01 Jan 2020 01:56:35 GMT
    Proxy-Connection: keep-alive
    複製代碼
  • 客戶端第一次拿到資源後,先將服務器告訴本身的緩存信息,保存起來,Cache-Control: public, max-age=0 讀取一下max-age,發現值等於0,這是告訴我應該每次都發真正的請求,而後再存一下Last-Modified上次更新的時間,再存一下ETag,這個告訴咱們資源的惟一id。

  • 而後客戶端開始發起第二次請求,請求頭以下:

    GET /demo.gif HTTP/1.1
    Host: 152.136.154.126:3000
    If-None-Match: W/"20f9a-16f5c76370d"
    Accept: */*
    If-Modified-Since: Tue, 31 Dec 2019 14:57:28 GMT
    User-Agent: SessionDownload/1 CFNetwork/1107.1 Darwin/19.0.0
    Accept-Language: en-us
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    複製代碼

能夠看到,客戶端將緩存資源的信息帶上來了,在If-None-Match,If-Modified-Since

  • 服務器再次接到這個請求頭,和本身的對資源的信息對比,發現If-None-MatchETag一致的,發現Last-ModifiedIf-Modified-Since一致的,那麼客戶端的資源就和服務器是一致的,我應該告訴客戶端304狀態碼,讓客戶端使用緩存就能夠了。 響應頭以下:
    HTTP/1.1 304 Not Modified
    X-Powered-By: Express
    Accept-Ranges: bytes
    Cache-Control: public, max-age=0
    Last-Modified: Tue, 31 Dec 2019 14:57:28 GMT
    ETag: W/"20f9a-16f5c76370d"
    Date: Wed, 01 Jan 2020 03:02:57 GMT
    Proxy-Connection: keep-alive
    複製代碼

自此,一次完整的HTTP協議緩存策略應用完成,咱們能夠看到,客戶端第一次發起請求,第二次發起請求,請求頭裏面的變化。和服務器如何對請求頭作出的校驗和響應。

我知道還有Expire字段,也是緩存相關的,可是HTTP1.1以後設置Cache-Control裏面的max-age,能夠覆蓋它,若是他們同時存在,因此這裏再也不概述,若是你感興趣,能夠WIKI,或者 http://47.99.237.180:8080/articles/2019/11/18/1574050998351.html

但願我能夠說清楚這個過程,由於下面iOS下面的網絡框架就會涉及到。 這個是HTTP協議的緩存策略,意味着,只要使用HTTP協議的客戶端,不論是瀏覽器,仍是移動端,仍是其餘,都使用。

即便如今不是特別清楚也不要緊,我會在文章中附上,整個過程的Demo,能夠運行Demo,來了解每一步。

4. 出了一個小插曲

在驗證過程當中除了一些小插曲,好比我已經爲服務器的靜態資源,設置資源的過時時間,在響應頭當中,Cache-Control: max-age=xxx,發如今移動端上,是OK的,在xxx秒以內再次訪問,真的沒有發起網絡請求,可是在Chrome當中我發現直接輸入資源地址,它好像會忽略過時時間同樣,都會發起真正的網絡請求,我看了小半天,我終於發現了,這個問題出在哪了,下面我來說一下。

  1. 移動端下: 使用URLRequest的默認緩存策略,緩存策略使用協議自己的緩存策略,設置超時時間工做正常。
  2. Web端: 使用標籤或者XHR發起的網絡請求,設置超時時間工做正常。
  3. Web端: 使用瀏覽器直接訪問資源,發現即便服務器設置了超時時間,仍是會發起網絡請求。

總結下來: 只有 3 顯示的不正常,問題可能就是由於直接在瀏覽器裏面輸入資源地址去訪問資源,忽略超時時間,可能因爲某些緣由吧,因此每次才都會發起網絡請求,並且Chrome和Safri的表現行爲還不同,因此咱們若是想驗證HTTP協議的緩存策略其實能夠忽略掉3,驗證12便可。

二,iOS下,URLRequest.CachePolicy和URLCache

上面咱們主要介紹了一些事先須要瞭解的知識,好比iOS裏面的URLRequest和URLRequest.CachePolicy使用範圍,還有最重要,也是最複雜的HTTP協議緩存策略。

0. CachePolicy 與 URLCache 的關係

CachePolicy:顧名思義這個是緩存策略的意思,我下面會仔細說它,這裏咱們知道它表明一種緩存的策略就OK。

URLCache: 這個其實就是咱們緩存的對象,也就是咱們使用了某種緩存策略,把內容存儲的地方和配置存儲在哪地方,等等,或者說管理URL緩存的對象。

小結: CachePolicy 用於選擇一種緩存策略,URLCache管理和設置緩存對象。

1. 有哪些地方能夠設置 CachePolicy

首先先了解 CachePolicy 有哪些策略可選:

  • useProtocolCachePolicy // 使用協議緩存,好比你的URLHTTP協議的,那就是使用HTTP協議的緩存策略,就是我上面講的,根據請求和響應頭的關鍵字,進行緩存的處理。 這也是(默認的策略)。
  • reloadIgnoringLocalCacheData // 徹底忽略本地緩存,直接從服務器拿資源。
  • reloadIgnoringLocalAndRemoteCacheData // 這個實例是未實現的,你不該該使用
  • reloadIgnoringCacheData // 這個選項已經被 reloadIgnoringLocalCacheData 選項所替代了。
  • returnCacheDataElseLoad // 若是本地有緩存就使用緩存,無論HTTP協議上緩存上的那些max-age,expire 過時時間等,沒有緩存再去遠端請求拿數據。這個協議存在的問題就是,若是使用HTTP協議的緩存策略,服務器沒辦法告訴它,它手頭的這份資料是舊的。
  • returnCacheDataDontLoad // 若是本地有緩存就使用緩存,本地沒有緩存就請求失敗,相似離線模式。
  • reloadRevalidatingCacheData // 這個實例是未實現的,你不該該使用它。

其實蘋果給的默認策略,一點毛病都沒有,根據HTTP協議的緩存策略來配置是最好的,有緩存使用緩存,沒有緩存,或者緩存過時,來請求服務器的資源。 也就是你根本毛都不用去設置,可是咱們常常會被問及,爲啥我服務器換了圖片,你移動端iOS不更新呢,若是你是默認策略,我能夠很負責的告訴你,你能夠狠狠的懟回去,你懂不懂HTTP協議的緩存策略,我這是官方的實現,而不是,不明道理,萎萎諾諾,好,我改爲一切都從服務器獲取。 不懂的哥們還覺得你技術不過關呢,不過說句實話,確實許多人真的不太清楚HTTP協議的緩存策略。

有哪些地方能夠設置緩存策略URLRequestCache呢? 其實看名字你就應該知道,這東西確定是給URLRequest設置的,我每個網絡請求,都會有一個URLRequest,可是,爲了方便,蘋果會有兩個地方給你設置這個策略的地方,分別是:

  • URLSessionConfiguration: 全部經過 URLSession 發出去的URLRequest 都會帶上這個策略。
    let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
            var request = URLRequest(url: url)
            let condig = URLSessionConfiguration.default
    
    	// 這 這 這 ⬇️ 這 這 這 ⬇️ 這 這 這 ⬇️ 這 這 這 ⬇️
            condig.requestCachePolicy = .reloadIgnoringLocalCacheData
            
            let session = URLSession(configuration: condig)
    複製代碼
  • URLRequest: 設置這個URLRequest的緩存策略,這個就沒什麼說的了。
    let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
    var request = URLRequest(url: url)
    
    // 這 這 這 ⬇️ 這 這 這 ⬇️ 這 這 這 ⬇️ 這 這 這 ⬇️
    request.cachePolicy = .returnCacheDataDontLoad
    
    let condig = URLSessionConfiguration.default
    let session = URLSession(configuration: condig)
    複製代碼

Q 疑問🤔️? 同時設置,URLSessionCondigurationURLRequest 的緩存策略,系統到底該用哪一個呢?

A 答案: URLRequest 的緩存策略會覆蓋掉 URLSessionCondiguration 的緩存策略。

2. 有哪些地方能夠設置 URLCache

區分 URLCacheNSCache nshipster.com/nsurlcache/ 這倆東西不是一回事哈,看名字你也知道了,一個是專門緩存URL的類,一個是相似Map,字典的存儲結構,用來緩存內存對象的。

上面也說了,URLCache 這個對象,實際上是用於管理URL的緩存的,好比放在哪裏呀,大小設置多少呀。

都哪些地方能夠設置呢? 有倆地方能夠設置,分別是:

  • URLSessionConfiguration: 單獨爲這個URLSession配置緩存對象,大小,路徑等,若是單獨爲URLSessionConfiguration配置了緩存對象,由這個URLSession發出去的URLRequest,都會使用這個緩存配置,不會使用全局的緩存對象。

    let condig = URLSessionConfiguration.default
    condig.requestCachePolicy = .reloadIgnoringLocalCacheData
    
    let cache = URLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024)
    condig.urlCache = cache
    複製代碼
  • URLCache: 類方法提供了全局的URLCache配置,當URLSessionCondiguration沒有另外設置的狀況下,會使用全局的緩存做爲本身的緩存。

    URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
    複製代碼

Q 疑問🤔️? 若是同時設置全局緩存URLSessionCondiguration緩存,系統有哪一個緩存?

A 回答: 確定使用URLSessionCondiguration的緩存,忽略全局緩存。

3. 幾種特殊場景

  1. URLSessionConfiguration 的緩存對象,空間設置爲0。

    let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
    var request = URLRequest(url: url)
    let condig = URLSessionConfiguration.default
    
    // 爲這個Session單獨設置緩存,而且空間都爲0,由於都是用默認緩存策略,也就是使用`HTTP協議`緩存,可是本地緩存沒有設置空間,因此每次都會從服務器拿去最新的資料。
    let cache = URLCache(memoryCapacity: 0, diskCapacity: 0)
    
    let session = URLSession(configuration: condig)
    let task = session.dataTask(with: request) { (data, resp, error) in
        let httpResp = resp as! HTTPURLResponse
        print("response (StatusCode):\(httpResp.statusCode)")
        DispatchQueue.main.async {
            let httpResponse = resp! as! HTTPURLResponse
            for (key,value) in httpResponse.allHeaderFields {
                print("\(key):\(value)")
            }
           self.imageView.image = UIImage.gifImageWithData(data: data! as NSData)
        }
    }
    task.resume()
    複製代碼
  2. URLCache全局緩存對象,空間設置爲0。

    // 由於URLSessionCondiguration的緩存沒有特殊設置,因此使用全局緩存,又由於全局緩存空間設置成0,又由於是默認緩存策略,因此每次都從服務器去拿最新資料。
    URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
    let url = URL(string: "http://152.136.154.126:3002/demo.gif")!
    var request = URLRequest(url: url)
    let condig = URLSessionConfiguration.default
    let session = URLSession(configuration: condig)
    let task = session.dataTask(with: request) { (data, resp, error) in
        let httpResp = resp as! HTTPURLResponse
        print("response (StatusCode):\(httpResp.statusCode)")
        DispatchQueue.main.async {
            let httpResponse = resp! as! HTTPURLResponse
            for (key,value) in httpResponse.allHeaderFields {
                print("\(key):\(value)")
            }
           self.imageView.image = UIImage.gifImageWithData(data: data! as NSData)
        }
    }
    task.resume()
    複製代碼

三,關於WKWebView和UIWebView裏面的請求緩存是怎樣的。

關於WebView這塊是這樣的,你設置的緩存策略,是頁面裏面的標籤的緩存策略,也就是,你雖然設置了緩存策略是: 不使用本地緩存,只是HTML加載的那些標籤使用這個策略,好比<img>,可是若是在HTML的JS發起的XHR,或者Fetch請求,使用的仍是 默認緩存策略,你不會改變到他們。 這是我用網絡攔截驗證到的結果,Demo在文章開頭和結尾都有。

攔截工具 項目地址: github.com/zColdWater/…

一,若是你想讓這個網站的標籤都使用默認的緩存策略,你就不須要另外設置。 也就是使用HTTP協議的緩存策略。

let url = URL(string: "https://www.baidu.com/")
var request = URLRequest(url: url!)
webview.load(request)
複製代碼

二,若是你想讓這個網站的資源標籤使用特定的緩存策略來請求,你能夠這樣處理。

let url = URL(string: "https://www.baidu.com/")
let request = URLRequest(url: self.url!, cachePolicy: .reloadIgnoringLocalCacheData)
webview.load(request)
複製代碼

小結: 若是文章不夠直觀,能夠下載Example項目,運行查看哦。

四,總結

其實對於iOS當中的URLRequest緩存,蘋果的默認策略就是很是合理的選擇,可是想要合理的使用這個默認策略,你須要瞭解HTTP協議的緩存策略,對於WebView的緩存策略咱們也說明了,我但願能夠幫到以前對這個緩存策略不清楚的童鞋,不要遇到問題一股腦就設置成不使用緩存了。 合理的使用緩存是最好的選擇。

但願我能夠說的清楚明白,若有不許確或者不對的地方,但願你們指正。

Example 項目地址: github.com/zColdWater/… 下載Demo,結合Demo一塊兒實踐更容易理解。

參考: blackpixel.com/writing/201…

相關文章
相關標籤/搜索