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