IETF 在 RFC 2616 中明確定義了 Web 瀏覽器與 Web 服務(wù)器之間的 HTTP 緩存的工作方式, 可以在 http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html 上找到相關(guān)信息. HTTP 被設(shè)計(jì)為針對(duì)瀏覽器與服務(wù)器之間的通信, 緩存機(jī)制也是針對(duì)這種使用模式的. iOS 提供了一種機(jī)制來利用標(biāo)準(zhǔn) HTTP 緩存, 以及采取相應(yīng)的行為. 通過 NSURLRequest 發(fā)出的每個(gè)請(qǐng)求都會(huì)經(jīng)過緩存組件. 該組件是 NSURLCache 或其子類的實(shí)例. 這個(gè)對(duì)象是 iOS 采用的管理來自服務(wù)器的響應(yīng)緩存的標(biāo)準(zhǔn)機(jī)制
1. 默認(rèn)緩存行為
在默認(rèn)情況下, NSURLRequest 遵照 RFC 2616 來管理緩存. 該默認(rèn)行為指定緩存要返回大多數(shù)當(dāng)前的內(nèi)容副本. 如果返回的內(nèi)容不是最新的, 就會(huì)發(fā)出警告; 如果無法返回內(nèi)容, 則報(bào)告錯(cuò)誤
在 iOS 上, 這意味著只有在響應(yīng)頭表明響應(yīng)能夠緩存的情況下, 當(dāng)第一次返回時(shí)才會(huì)將其緩存到內(nèi)存中. 對(duì)于后續(xù)發(fā)送給相同 URL 的請(qǐng)求來說, iOS 會(huì)使用 If-Modified-Since 頭(包含緩存響應(yīng)的修改日期與時(shí)間)向服務(wù)器發(fā)送請(qǐng)求. 如果服務(wù)器確定自從請(qǐng)求頭提供的時(shí)間開始內(nèi)容沒有被修改, 就會(huì)返回狀態(tài)為 304 的響應(yīng), 并且沒有響應(yīng)體. 通過這個(gè)響應(yīng), iOS 能夠確定它所緩存的副本是最新的內(nèi)容, 并使用 200 狀態(tài)碼返回, 這很有效地對(duì)應(yīng)用代碼隱藏了緩存活動(dòng). 在內(nèi)容來自于內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)的網(wǎng)絡(luò)配置中, 源 URL 對(duì)于不同的請(qǐng)求來說可能是不同的, 因此對(duì)于 HTTP 標(biāo)準(zhǔn)定義的緩存機(jī)制是不適用的
這些標(biāo)準(zhǔn)的緩存規(guī)則的設(shè)計(jì)專門針對(duì)與 Web 瀏覽器的交互. 使用 HTTP 作為傳輸協(xié)議的移動(dòng)應(yīng)用可以適當(dāng)修改這些規(guī)則來改進(jìn)性能并滿足應(yīng)用的需求. iOS 中的 URL 加載系統(tǒng)向客戶端應(yīng)用提供了一種方式來覆寫默認(rèn)行為. 在覆寫默認(rèn)行為時(shí), 你需要花一些時(shí)間來充分理解可能會(huì)導(dǎo)致應(yīng)用出現(xiàn)缺陷的邊際行為
可以通過為請(qǐng)求設(shè)定緩存策略來覆寫默認(rèn)的緩存規(guī)則
iOS 提供了 6 種不同的設(shè)置, 使得開發(fā)者能夠控制響應(yīng)緩存的方式:
- NSURLRequestUseProtocolCachePolicy - 該設(shè)置告訴系統(tǒng)遵照 RFC 2616 指定的規(guī)則
- NSURLRequestReloadIgnoringLocalCacheData - 該設(shè)置告訴請(qǐng)求略過本地緩存, 從網(wǎng)絡(luò)上檢索新的內(nèi)容. 如果某些網(wǎng)絡(luò)設(shè)備(如緩存網(wǎng)絡(luò)代理)介于應(yīng)用與數(shù)據(jù)源之間, 并且持有內(nèi)容的緩存副本, 就會(huì)返回緩存副本
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData - 該設(shè)置告訴請(qǐng)求略過本地緩存并將頭添加到請(qǐng)求中, 同時(shí)告訴中間設(shè)備也略過緩存, 提供源服務(wù)器上的最新數(shù)據(jù)
- NSURLRequestReturnCacheDataElseLoad - 該設(shè)置會(huì)讓緩存系統(tǒng)返回一份內(nèi)容的緩存副本而不去驗(yàn)證服務(wù)器上是否有最新的副本. 如果請(qǐng)求的緩存副本在緩存中存在, 就會(huì)將其返回. 如果緩存副本不存在, 那就通過網(wǎng)絡(luò)請(qǐng)求檢索內(nèi)容. 該設(shè)置提供了最快的響應(yīng)時(shí)間, 但卻最有可能返回過期的數(shù)據(jù). 要想通過該設(shè)置帶來收益, 請(qǐng)使用該類型的請(qǐng)求向用戶提供最初的快速響應(yīng), 然后在后臺(tái)線程中發(fā)出請(qǐng)求, 使用服務(wù)器的最新數(shù)據(jù)來刷新緩存
- NSURLRequestReturnCacheDataDontLoad - 該設(shè)置指定只返回緩存中的內(nèi)容. 如果內(nèi)容不在緩存中, 那就會(huì)返回錯(cuò)誤而不是從服務(wù)器上獲取內(nèi)容
- NSURLRequestReloadRevalidatingCacheData - 該設(shè)置總是會(huì)重新驗(yàn)證數(shù)據(jù). 在某些情況下, 緩存的響應(yīng)可能會(huì)有過期時(shí)間, 到了這個(gè)時(shí)間后系統(tǒng)就會(huì)檢查最新的數(shù)據(jù). 如果使用該設(shè)置, 那就會(huì)忽略掉過期時(shí)間, 并且總是驗(yàn)證服務(wù)器有沒有最新的內(nèi)容
除了配置每個(gè)請(qǐng)求使用緩存的方式外, 還可以通過配置應(yīng)用的 NSURLCache 對(duì)象來指定應(yīng)用所能緩存的數(shù)據(jù)量
2. 配置 NSURLCache
在應(yīng)用使用任何標(biāo)準(zhǔn)的 iOS 類創(chuàng)建網(wǎng)絡(luò)請(qǐng)求時(shí), 系統(tǒng)都會(huì)創(chuàng)建 NSURLCache 實(shí)例. 在默認(rèn)情況下, 該實(shí)例只會(huì)將數(shù)據(jù)緩存在 RAM 中, 這意味著當(dāng)程序退出時(shí), 其緩存的請(qǐng)求就會(huì)被清空. 當(dāng)設(shè)備處于低內(nèi)存狀態(tài)時(shí)也會(huì)清空 RAM 緩存
iOS 提供了一種方式來重新定義默認(rèn)的緩存, 并指定了更大的內(nèi)存容量與持久化存儲(chǔ), 以便緩存在應(yīng)用重啟后依然可以使用.
該例創(chuàng)建 1MB 的內(nèi)存緩存和 20MB 的持久化緩存. 緩存數(shù)據(jù)庫(kù)的位置位于應(yīng)用的沙箱, 在 Library/Caches 目錄下, 文件名為 URLCache. 示例代碼的第 2 行將應(yīng)用的緩存實(shí)例設(shè)定為上一行創(chuàng)建的那一個(gè)
iOS 中有一種奇怪的現(xiàn)象, 即在某些情況下, 應(yīng)用中的系統(tǒng)組件會(huì)將緩存的內(nèi)存容量設(shè)為 0MB, 這就禁用了緩存. 解決了這個(gè)無法解釋的行為的一種方式就是通過自己的實(shí)現(xiàn)子類化 NSURLCache, 拒絕將內(nèi)存緩存大小設(shè)為 0.?
setMemoryCapacity: 方法中的代碼會(huì)驗(yàn)證大小不為 0, 并調(diào)用父類, 從而將新的大小設(shè)為除了 0 之外的其他值
可以通過壓縮數(shù)據(jù)及管理化請(qǐng)求以最大化地提升應(yīng)用的性能, 不過最快的請(qǐng)求實(shí)際上是沒有發(fā)出的請(qǐng)求. 通過仔細(xì)考慮應(yīng)用需求以及服務(wù)器的行為, 可以將數(shù)據(jù)保留在緩存中, 只有當(dāng)服務(wù)器上的數(shù)據(jù)發(fā)生變化時(shí)才刷新, 從而避免發(fā)出這些請(qǐng)求
7.3 小結(jié)
iOS 用戶都希望應(yīng)用能夠立刻響應(yīng)每個(gè)請(qǐng)求. 移動(dòng)產(chǎn)業(yè)有這樣一條原則, 即屏幕越小, 用戶越?jīng)]耐心. 提供讓用戶樂于使用的應(yīng)用意味著要珍惜用戶的時(shí)間, 就像珍惜你自己的時(shí)間一樣. 通過壓縮請(qǐng)求與響應(yīng)來優(yōu)化應(yīng)用所使用的帶寬, 通過管道化請(qǐng)求避免不必要的延遲, 甚至通過緩存響應(yīng)來避免冗余的網(wǎng)絡(luò)請(qǐng)求都會(huì)加速應(yīng)用并改進(jìn)用戶體驗(yàn)