Example 項目地址: github.com/zColdWater/… 下載Demo,結合Demo一塊兒實踐更容易理解。html
在開始以前,咱們須要清楚下面的一些問題,才方便咱們後面的講解。git
咱們一提到URLRequest
,我相信不少國內的開發者,首先就會聯想到,HTTP
請求,而後木有別的了。 可是其實 URLRequest
是一個很大的概念,它不僅服務於HTTP
協議,它還服務於 其餘應用協議,好比File
協議,Data
協議,自定義協議等等。 要麼蘋果公司爲何不叫它HTTPURLRequest
呢? 問題就在於咱們平時最常接觸的就是HTTP
協議,用來請求服務端的數據用來展現。github
經過上面的文章咱們清楚了 URLRequest
服務不少協議,那麼URLRequest.CachePolicy
的範圍是什麼呢,很明顯,和URLRequest
同樣,這個緩存策略也包含上面這些協議,固然 我也不清楚其餘協議的緩存策略是什麼樣子的,好比File
協議,或則別的。 可是我很清楚,咱們經常使用的HTTP
協議的緩存協議,這個後面再講,這裏清楚的是,這個緩存策略支持不少協議,咱們的HTTP
協議有着本身的緩存策略。web
我以前轉了一篇臺灣做者關於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
協議規則進行檢查,來決定是讓客戶端使用緩存仍是使用服務器下發的資源。
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
複製代碼
這裏注意的是,在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
複製代碼
Cache-Control
: max-age = X (max-age=x 是告訴客戶端x秒以內不要再發起請求了,就用你的緩存就OK了,換句話說,若是服務器告訴客戶端max-age=100,客戶端在100s以內再去請求,是不會發起真正的網絡請求的,客戶端的網絡層框架會自動返回狀態碼200,上一次的緩存數據)Last-Modified
: Tue, 31 Dec 2019 14:57:28 GMT(這個字段是告訴客戶端,這個資源最後一次更新的時間,讓客戶端保存好,下一次請求的時候,在請求頭裏面帶上這個值,請求頭的那個字段就是If-Modified-Since
,這裏的規則是這樣的,若是請求頭裏的If-Modified-Since
時間點早於服務器的Last-Modified
時間點,服務器會返回200,讓客戶端須要更新最新資源,若是反過來,或者相同,服務器會下發304,讓客戶端使用緩存。)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-Match
和ETag
一致的,發現Last-Modified
和If-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,來了解每一步。
在驗證過程當中除了一些小插曲,好比我已經爲服務器的靜態資源,設置資源的過時時間,在響應頭當中,
Cache-Control: max-age=xxx
,發如今移動端上,是OK的,在xxx秒以內再次訪問,真的沒有發起網絡請求,可是在Chrome
當中我發現直接輸入資源地址,它好像會忽略過時時間同樣,都會發起真正的網絡請求,我看了小半天,我終於發現了,這個問題出在哪了,下面我來說一下。
標籤
或者XHR
發起的網絡請求,設置超時時間工做正常。總結下來: 只有 3
顯示的不正常,問題可能就是由於直接在瀏覽器裏面輸入資源地址去訪問資源,忽略超時時間,可能因爲某些緣由吧,因此每次才都會發起網絡請求,並且Chrome和Safri的表現行爲還不同,因此咱們若是想驗證HTTP協議的緩存策略其實能夠忽略掉3
,驗證1
和2
便可。
上面咱們主要介紹了一些事先須要瞭解的知識,好比iOS裏面的URLRequest和URLRequest.CachePolicy使用範圍,還有最重要,也是最複雜的HTTP
協議緩存策略。
CachePolicy
:顧名思義這個是緩存策略的意思,我下面會仔細說它,這裏咱們知道它表明一種緩存的策略就OK。
URLCache
: 這個其實就是咱們緩存的對象,也就是咱們使用了某種緩存策略,把內容存儲的地方和配置存儲在哪地方,等等,或者說管理URL緩存的對象。
小結: CachePolicy
用於選擇一種緩存策略,URLCache
管理和設置緩存對象。
首先先了解 CachePolicy
有哪些策略可選:
useProtocolCachePolicy
// 使用協議緩存,好比你的URL
是HTTP
協議的,那就是使用HTTP
協議的緩存策略,就是我上面講的,根據請求和響應頭的關鍵字,進行緩存的處理。 這也是(默認的策略)。reloadIgnoringLocalCacheData
// 徹底忽略本地緩存,直接從服務器拿資源。reloadIgnoringLocalAndRemoteCacheData
// 這個實例是未實現的,你不該該使用reloadIgnoringCacheData
// 這個選項已經被 reloadIgnoringLocalCacheData 選項所替代了。returnCacheDataElseLoad
// 若是本地有緩存就使用緩存,無論HTTP協議上緩存上的那些max-age,expire 過時時間等,沒有緩存再去遠端請求拿數據。這個協議存在的問題就是,若是使用HTTP協議的緩存策略,服務器沒辦法告訴它,它手頭的這份資料是舊的。returnCacheDataDontLoad
// 若是本地有緩存就使用緩存,本地沒有緩存就請求失敗,相似離線模式。reloadRevalidatingCacheData
// 這個實例是未實現的,你不該該使用它。其實蘋果給的默認策略,一點毛病都沒有,根據HTTP協議的緩存策略來配置是最好的,有緩存使用緩存,沒有緩存,或者緩存過時,來請求服務器的資源。 也就是你根本毛都不用去設置,可是咱們常常會被問及,爲啥我服務器換了圖片,你移動端iOS不更新呢,若是你是默認策略,我能夠很負責的告訴你,你能夠狠狠的懟回去,你懂不懂HTTP協議的緩存策略,我這是官方的實現,而不是,不明道理,萎萎諾諾,好,我改爲一切都從服務器獲取。 不懂的哥們還覺得你技術不過關呢,不過說句實話,確實許多人真的不太清楚
HTTP協議
的緩存策略。
有哪些地方能夠設置緩存策略URLRequestCache
呢? 其實看名字你就應該知道,這東西確定是給URLRequest
設置的,我每個網絡請求,都會有一個URLRequest
,可是,爲了方便,蘋果會有兩個地方給你設置這個策略的地方,分別是:
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
的緩存策略,這個就沒什麼說的了。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 疑問🤔️? 同時設置,URLSessionCondiguration
和 URLRequest
的緩存策略,系統到底該用哪一個呢?
A 答案: URLRequest
的緩存策略會覆蓋掉 URLSessionCondiguration
的緩存策略。
區分
URLCache
和NSCache
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
的緩存,忽略全局緩存。
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()
複製代碼
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()
複製代碼
關於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一塊兒實踐更容易理解。