轉載:https://blog.csdn.net/jason_chen13/article/details/51984823
兩行代碼就能完成80%的緩存需求
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
}
剩下20%的網絡緩存需求
NSURLCache 不是幫我們做了硬盤緩存么?那我們為什么要自己用數據庫做本地緩存啊。為啥不直接用NSURLCache 不是更方便?
真的有NSURLCache
不能滿足的需求?
有人可能要問:
系統幫我們做的緩存,好處是自動,無需我們進行復雜的設置。壞處也恰恰是這個:不夠靈活,不能自定義。只能指定一個緩存的總文件夾,不能分別指定每一個文件緩存的位置,更不能為每個文件創建一個文件夾,也不能指定文件夾的名稱。緩存的對象也是固定的:只能是 GET請求的返回值
目錄
由于微信、QQ、微博、這類的應用使用緩存很“重”,使一般的用戶也對緩存也非常習慣。緩存已然成為必備。
緩存的目的的以空間換時間
這句話在動輒就是 300M、600M 的大應用上,得到了很好的詮釋。但能有緩存意識的公司,還在少數。
只有你真正感受到痛的時候,你才會考慮使用緩存。
這個痛可能是:
服務器壓力、客戶端網絡優化、用戶體驗等等。
當我們在談論緩存的時候,我們在談論什么?
我們今天將站在小白用戶的角度,給緩存這個概念進行重新的定義。
緩存有不同的分類方法:
這里所指的緩存,是一個寬泛的概念。
我們這里主要按照功能進行劃分:
- | 第一種 | 第二種 |
---|---|---|
目的 | 優化型緩存 | 功能型緩存 |
具體描述 | 出于優化考慮:服務器壓力、用戶體驗、為用戶剩流量等等。同時優化型緩存也有內存緩存和磁盤緩存之分。 | App離線也能查看,出于功能考慮,屬于存儲范疇 |
常見概念 | GET網絡請求緩存、WEB緩存 | 離線存儲 |
典型應用 | 微信首頁的會話列表、微信頭像、朋友圈、網易新聞新聞列表、 | 微信聊天記錄、 |
Parse對應的類 | PFCachedQueryController | PFOfflineStore |
重度使用緩存的App: 微信、微博、網易新聞、攜程、去哪兒等等。
GET網絡請求緩存
概述
首先要知道,POST請求不能被緩存,只有 GET 請求能被緩存。因為從數學的角度來講,GET 的結果是 冪等
的,就好像字典里的 key 與 value 就是冪等
的,而 POST 不 冪等
。緩存的思路就是將查詢的參數組成的值作為 key ,對應結果作為value。從這個意義上說,一個文件的資源鏈接,也叫 GET 請求,下文也會這樣看待。
80%的緩存需求:兩行代碼就可滿足
設置緩存只需要三個步驟:
第一個步驟:請使用 GET 請求。
第二個步驟:
如果你已經使用 了 GET 請求,iOS 系統 SDK 已經幫你做好了緩存。你需要的僅僅是設置下內存緩存大小、磁盤緩存大小、以及緩存路徑。甚至這兩行代碼不設置也是可以的,會有一個默認值。代碼如下:
第三個步驟:沒有第三步!
你只要設置了這兩行代碼,基本就可滿足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的屬性標記此文件在服務期端最后被修改的時間,格式類似這樣:
客戶端第二次請求此URL時,根據 HTTP 協議的規定,瀏覽器會向服務器傳送 If-Modified-Since 報頭,詢問該時間之后文件是否有被修改過:
總結下來它的結構如下:
請求 HeaderValue | 響應 HeaderValue |
---|---|
Last-Modified | If-Modified-Since |
如果服務器端的資源沒有變化,則自動返回 HTTP 304 (Not Changed.)狀態碼,內容為空,這樣就節省了傳輸數據量。當服務器端代碼發生改變或者重啟服務器時,則重新發出資源,返回和第一次請求時類似。從而保證不向客戶端重復發出資源,也保證當服務器有變化時,客戶端能夠得到最新的資源。
判斷方法用偽代碼表示:
之所以使用
而非使用:
原因是考慮到可能出現類似下面的情況:服務端可能對資源文件,廢除其新版,回滾啟用舊版本,此時的情況是:
但我們依然要更新本地緩存。
參考鏈接: What takes precedence: the ETag or Last-Modified HTTP header?
Demo10和 Demo11 給出了一個完整的校驗步驟:
并給出了 NSURLConnection
和 NSURLSession
兩個版本:
ETag
ETag
是什么?
HTTP 協議規格說明定義ETag為“被請求變量的實體值” (參見 —— 章節 14.19)。 另一種說法是,ETag是一個可以與Web資源關聯的記號(token)。它是一個 hash 值,用作 Request 緩存請求頭,每一個資源文件都對應一個唯一的 ETag
值,
服務器單獨負責判斷記號是什么及其含義,并在HTTP響應頭中將其傳送到客戶端,以下是服務器端返回的格式:
其中:
-
If-None-Match
- 與響應頭的 Etag 相對應,可以判斷本地緩存數據是否發生變化
總結下來它的結構如下:
請求 HeaderValue | 響應 HeaderValue |
---|---|
ETag | If-None-Match |
ETag
是的功能與 Last-Modified
類似:服務端不會每次都會返回文件資源。客戶端每次向服務端發送上次服務器返回的 ETag
值,服務器會根據客戶端與服務端的 ETag
值是否相等,來決定是否返回 data,同時總是返回對應的 HTTP
狀態碼。客戶端通過 HTTP
狀態碼來決定是否使用緩存。比如:服務端與客戶端的 ETag
值相等,則 HTTP
狀態碼為 304,不返回 data。服務端文件一旦修改,服務端與客戶端的 ETag
值不等,并且狀態值會變為200,同時返回 data。
因為修改資源文件后該值會立即變更。這也決定了 ETag
在斷點下載時非常有用。
比如 AFNetworking 在進行斷點下載時,就是借助它來檢驗數據的。詳見在 AFHTTPRequestOperation
類中的用法:
七牛等第三方文件存儲商現在都已經支持ETag
,Demo8和9 中給出的演示圖片就是使用的七牛的服務,見:
下面使用一個 Demo 來進行演示用法,
以 NSURLConnection
搭配 ETag
為例,步驟如下:
- 請求的緩存策略使用
NSURLRequestReloadIgnoringCacheData
,忽略本地緩存 - 服務器響應結束后,要記錄
Etag
,服務器內容和本地緩存對比是否變化的重要依據 - 在發送請求時,設置
If-None-Match
,并且傳入Etag
- 連接結束后,要判斷響應頭的狀態碼,如果是
304
,說明本地緩存內容沒有發生變化
以下代碼詳見 Demo08 :
相應的 NSURLSession
搭配 ETag 的版本見 Demo09:
運行效果:
總結
在官方給出的文檔中提出 ETag
是首選的方式,優于 Last-Modified
方式。因為 ETag
是基于 hash ,hash 的規則可以自己設置,而且是基于一致性,是“強校驗”。 Last-Modified
是基于時間,是弱校驗,弱在哪里?比如說:如果服務端的資源回滾客戶端的 Last-Modified
反而會比服務端還要新。
雖然 ETag
優于 Last-Modified
,但并非所有服務端都會支持,而 Last-Modified
則一般都會有該字段。 大多數情況下需要與服務端進行協調支持 ETag
,如果協商無果就只能退而求其次。
Demo 也給出了一個不支持 ETag
的鏈接,基本隨便找一張圖片都行:
作為通用型的網絡請求工具 AFNetworking 對該現狀的處理方式是,判斷服務端是否包含 ETag
,然后再進行相應處理。可見 AFHTTPRequestOperation
類中的用法,也就是上文中已經給出的斷點下載的代碼。
在回顧下思路:
- 為資源分派 hash 值,然后對比服務端與本地緩存是否一致來決定是否需要更新緩存。
這種思路,在開發中經常使用,比如:處于安全考慮,登陸操作一般不會傳輸賬號密碼,而是傳輸對應的 hash 值– token ,這里的 token 就可以看做一個 file 資源,如果想讓一個用戶登陸超時時間是三天,只需要在服務端每隔三天更改下 token 值,客戶端與服務端值不一致,然后服務端返回 token 過期的提示。
值得注意的一點是:
- 如果借助了
Last-Modified
和ETag
,那么緩存策略則必須使用NSURLRequestReloadIgnoringCacheData
策略,忽略緩存,每次都要向服務端進行校驗。
如果 GET 中包含有版本號信息
眾多的應用都會在 GET 請求后加上版本號:
這種情況下,
?v1.0
和 ?v2.0
兩個不同版本,請求到的 Last-Modified
和 ETag
會如預期嗎?
這完全取決于公司服務端同事的實現, Last-Modified
和 ETag
僅僅是一個協議,并沒有統一的實現方法,而服務端的處理邏輯完全取決于需求。
你完全可以要求服務端同事,僅僅判斷資源的異同,而忽略掉 ?v1.0
和 ?v2.0
兩個版本的區別。
參考鏈接:if-modified-since vs if-none-match
一般數據類型借助 Last-Modified
與 ETag
進行緩存
以上的討論是基于文件資源,那么對一般的網絡請求是否也能應用?
控制緩存過期時間,無非兩種:設置一個過期時間;校驗緩存與服務端一致性,只在不一致時才更新。
一般情況下是不會對 api 層面做這種校驗,只在有業務需求時才會考慮做,比如:
- 數據更新頻率較低,“萬不得已不會更新”—只在服務器有更新時才更新,以此來保證2G 等惡略網絡環境下,有較好的體驗。比如網易新聞欄目,但相反微博列表、新聞列表就不適合。
- 業務數據一致性要求高,數據更新后需要服務端立刻展示給用戶。客戶端顯示的數據必須是服務端最新的數據
- 有離線展示需求,必須實現緩存策略,保證弱網情況下的數據展示的速度。但不考慮使用緩存過期時間來控制緩存的有效性。
- 盡量減少數據傳輸,節省用戶流量
一些建議:
如果是 file 文件類型,用
Last-Modified
就夠了。即使ETag
是首選,但此時兩者效果一致。九成以上的需求,效果都一致。-
如果是一般的數據類型–基于查詢的 get 請求,比如返回值是 data 或 string 類型的 json 返回值。那么
Last-Modified
服務端支持起來就會困難一點。因為比如
你做了一個博客瀏覽 app ,查詢最近的10條博客, 基于此時的業務考慮Last-Modified
指的是10條中任意一個博客的更改。那么服務端需要在你發出請求后,遍歷下10條數據,得到“10條中是否至少一個被修改了”。而且要保證每一條博客表數據都有一個類似于記錄Last-Modified
的字段,這顯然不太現實。如果更新頻率較高,比如最近微博列表、最近新聞列表,這些請求就不適合,更多的處理方式是添加一個接口,客戶端將本地緩存的最后一條數據的的時間戳或 id 傳給服務端,然后服務端會將新增的數據條數返回,沒有新增則返回 nil 或 304。
參考鏈接: 《(慕課網)imooc iPhone3.3 接口數據緩存》
轉自:http://prolove10.blog.163.com/blog/static/138411843201443111235812/
轉自:http://www.cocoachina.com/ios/20150626/12161.html
iOS根本離不開網絡——不論是從服務端讀寫數據、向系統分發計算任務,還是從云端加載圖片、音頻、視頻等。
當應用程序面臨處理問題的抉擇時,通常會選擇最高級別的框架來解決這個問題。所以如果給定的任務是通過http://,
https:// 或 ftp://進行通訊,那么與NSURLConnection 相關的方法就是最好的選擇了。
蘋果關于網絡的類涵蓋甚廣,包括從URL加載、還存管理到認證與存儲cookie等多個領域,完全可以滿足現代Objective-C應用開發的需要:
URL加載
NSURLConnection
NSURLRequest NSMutableURLRequest
NSURLResponse NSHTTPURLResponse
緩存管理
NSURLCache
NSCacheURLRequest
NSCachedURLResponse
認證 & 證書
NSURLCredential
NSURLCredentialStorage
NSURLAuthenticationChallenge
NSURLProtectionSpace
Cookie存儲
NSHTTPCookie
NSHTTPCookieStorage
協議支持
NSURLProtocol
雖然URL加載系統包含的內容眾多,但代碼的設計上卻非常良好,沒有把復雜的操作暴露出來,開發者只需要在用到的時候進行設置。
任何通過NSURLConnection 進行的請求都會被系統的其他部分所攔截,這也使得當可用時顯式地從硬盤加載緩存成為了可能。
說到這里,我們就說說:NSURLProtocol。
NSURLProtocol
NSURLProtocol或許是URL加載系統中最功能強大但同時也是最晦澀的部分了。它是一個抽象類,
你可以通過子類化來定義新的或已經存在的URL加載行為。
聽了我說了這些亂七八糟的如果你還沒有抓狂,這里有一些關于希望加載請求時不用改變其他部分代碼的例子,供你參考:
攔截圖片加載請求,轉為從本地文件加載
為了測試對HTTP返回內容進行mock和stub
對發出請求的header進行格式化
對發出的媒體請求進行簽名
創建本地代理服務,用于數據變化時對URL請求的更改
故意制造畸形或非法返回數據來測試程序的魯棒性
過濾請求和返回中的敏感信息
在既有協議基礎上完成對 NSURLConnection的實現且與原邏輯不產生矛盾
再次強調 NSURLProtocol
核心思想最重要的一點:用了它,你不必改動應用在網絡調用上的其他部分,就可以改變URL加載行為的全部細節。
或者這么說吧: NSURLProtocol 就是一個蘋果允許的中間人攻擊。
子類化NSURLProtocol
之前提到過 NSURLProtocol是一個抽象類,所以不能夠直接使用必須被子類化之后才能使用。
讓子類識別并控制請求
子類化 NSURLProtocol 的第一個任務就是告訴它要控制什么類型的網絡請求。比如說如果你想要當本地有資源的時候請求直接使用本地資源文件,
那么相關的請求應該對應已有資源的文件名。
這部分邏輯定義在 +canInitWithRequest: 中,如果返回 YES,該請求就會被其控制。返回 NO 則直接跳入下一Protocol。
提供請求規范
如果你想要用特定的某個方式來修改一個請求,應該使用 +canonicalRequestForRequest: 方法。每一個subclass都應該依據某一個規范,
也就是說,一個protocol應該保證只有唯一的規范格式(雖然很多不同的請求可能是同一種規范格式)。
獲取和設置請求的屬性
NSURLProtocol提供方法允許你來添加、獲取、刪除一個request對象的任意metadata,而且不需要私有擴展或者方法欺騙(swizzle):
+propertyForKey:inRequest:
+setProperty:forKey:inRequest:
+removePropertyForKey:inRequest:
在操作protocol時對尚未賦予特定信息的 NSURLRequest
進行操作時,上述方法都是特別重要的。這些對于和其他方法之間的狀態傳遞也非常有用。
加載請求
你的子類中最重要的方法就是 -startLoading 和 -stopLoading。不同的自定義子類在調用這兩個方法是會傳入不同的內容,
但共同點都是要圍繞protocol客戶端進行操作。
每個 NSURLProtocol 的子類實例都有一個 client 屬性,該屬性對URL加載系統進行相關操作。它不是NSURLConnection,
但看起來和一個實現了NSURLConnectionDelegate 協議的對象非常相似。
<NSURLProtocolClient>
-URLProtocol:cachedResponseIsValid:
-URLProtocol:didCancelAuthenticationChallenge:
-URLProtocol:didFailWithError:
-URLProtocol:didLoadData:
-URLProtocol:didReceiveAuthenticationChallenge:
-URLProtocol:didReceiveResponse:cacheStoragePolicy:
-URLProtocol:wasRedirectedToRequest:redirectResponse:
-URLProtocolDidFinishLoading:
在對-startLoading 和-stopLoading的實現中,你需要在恰當的時候讓client調用每一個delegate方法。
簡單來說就是連續調用那些方法,不過這是至關重要的。
向URL加載系統注冊子類
最后,為了使用 NSURLProtocol 子類,需要向URL加載系統進行注冊。
當請求被加載時,系統會向每一個注冊過的protocol詢問:“Hey你能控制這個請求嗎?”第一個通過
+canInitWithRequest: 回答為 YES 的protocol就會控制該請求。URLprotocol會被以注冊順序的反序訪問,所以當在
-application:didFinishLoadingWithOptions:方法中調用 [NSURLProtocol registerClass:[MyURLProtocol class]]; 時,
你自己寫的protocol比其他內建的protocol擁有更高的優先級。
就像控制請求的URL加載系統一樣, NSURLProtocol也一樣的無比強大,可以通過各種靈活的方式使用。它作為一個相對晦澀難解的類,
我們挖掘出了它的潛力來讓我們的代碼更清爽健壯。
NSURLCache
NSURLCache 為您的應用的 URL 請求提供了內存中以及磁盤上的綜合緩存機制。 作為基礎類庫 URL 加載系統 的一部分,
任何通過 NSURLConnection加載的請求都將被 NSURLCache 處理。
網絡緩存減少了需要向服務器發送請求的次數,同時也提升了離線或在低速網絡中使用應用的體驗。
當一個請求完成下載來自服務器的回應,一個緩存的回應將在本地保存。下一次同一個請求再發起時,本地保存的回應就會馬上返回,
不需要連接服務器。NSURLCache會 自動 且 透明 地返回回應。
初始化并設置一個共享的 URL 緩存
為了好好利用 NSURLCache,你需要初始化并設置一個共享的 URL 緩存。在 iOS 中這項工作需要在
-application:didFinishLaunchingWithOptions: 完成,而 Mac OS X 中是在
–applicationDidFinishLaunching::
例如:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
}
緩存策略由請求(客戶端)和回應(服務端)分別指定。理解這些策略以及它們如何相互影響,是為您的應用程序找到最佳行為的關鍵。
NSURLRequestCachePolicy NSURLRequest 有個 cachePolicy屬性,它根據以下常量指定了請求的緩存行為:
NSURLRequestUseProtocolCachePolicy:對特定的 URL 請求使用網絡協議中實現的緩存邏輯。這是默認的策略。
NSURLRequestReloadIgnoringLocalCacheData:數據需要從原始地址加載。不使用現有緩存。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不僅忽略本地緩存,同時也忽略代理服務器或其他中間介質目前已有的、協議允許的緩存。
NSURLRequestReturnCacheDataElseLoad:無論緩存是否過期,先使用本地緩存數據。如果緩存中沒有請求所對應的數據,那么從原始地址加載數據。
NSURLRequestReturnCacheDataDontLoad:無論緩存是否過期,先使用本地緩存數據。如果緩存中沒有請求所對應的數據,那么放棄從原始地址加載數據,
請求視為失敗(即:“離線”模式)。
NSURLRequestReloadRevalidatingCacheData:從原始地址確認緩存數據的合法性后,緩存數據就可以使用,否則從原始地址加載。
你并不會驚奇于這些值不被透徹理解且經常搞混淆。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData 和NSURLRequestReloadRevalidatingCacheData
根本沒有實現(Link to Radar)更加加深了混亂程度!
關于NSURLRequestCachePolicy,以下才是你實際 需要了解的東西:
UseProtocolCachePolicy 默認行為
ReloadIgnoringLocalCacheData 不使用緩存
ReloadIgnoringLocalAndRemoteCacheData 我是認真地,不使用任何緩存
ReturnCacheDataElseLoad 使用緩存(不管它是否過期),如果緩存中沒有,那從網絡加載吧
ReturnCacheDataDontLoad 離線模式:使用緩存(不管它是否過期),但是不從網絡加載
ReloadRevalidatingCacheData 在使用前去服務器驗證
HTTP 緩存語義
因為 NSURLConnection 被設計成支持多種協議——包括 FTP、HTTP、HTTPS——所以
URL加載系統用一種協議無關的方式指定緩存。為了本文的目的,緩存用術語 HTTP 語義來解釋。
HTTP 請求和回應用
headers 來交換元數據,如字符編碼、MIME 類型和緩存指令等。
Request Cache Headers
在默認情況下,NSURLRequest會用當前時間決定是否返回緩存的數據。為了更精確地控制,允許使用以下請求頭:
If-Modified-Since - 這個請求頭與 Last-Modified回應頭相對應。把這個值設為同一終端最后一次請求時返回的 Last-Modified 字段的值。
If-None-Match - 這個請求頭與與Etag 回應頭相對應。使用同一終端最后一次請求的Etag 值。
Response Cache Headers
NSHTTPURLResponse 包含多個 HTTP 頭,當然也包括以下指令來說明回應應當如何緩存:
Cache-Control - 這個頭必須由服務器端指定以開啟客戶端的HTTP 緩存功能。這個頭的值可能包含 max-age(緩存多久),
是公共 public 還是私有 private,或者不緩存 no-cache 等信息。詳情請參閱 Cache-Control section of RFC 2616。
除了 Cache-Control 以外,服務器也可能發送一些附加的頭用于根據需要有條件地請求(如上一節所提到的):
Last-Modified -
這個頭的值表明所請求的資源上次修改的時間。例如,一個客戶端請求最近照片的時間線,/photos/timeline,
Last-Modified的值可以是最近一張照片的拍攝時間。
Etag - 這是 “entity tag”的縮寫,它是一個表示所請求資源的內容的標識符。在實踐中,Etag
的值可以是類似于資源的 MD5 之類的東西。這對于那些動態生成的、可能沒有明顯的 Last-Modified
值的資源非常有用。
NSURLConnectionDelegate
一旦收到了服務器的回應,NSURLConnection 的代理就有機會在 -connection:willCacheResponse: 中指定緩存數據。
NSCachedURLResponse 是個包含NSURLResponse 以及它對應的緩存中的 NSData 的類。
在 -connection:willCacheResponse:中,cachedResponse 對象會根據 URL連接返回的結果自動創建。因為 NSCachedURLResponse沒有可變部分,
為了改變 cachedResponse 中的值必須構造一個新的對象,把改變過的值傳入
–initWithResponse:data:userInfo:storagePolicy:
例如:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];
NSMutableData *mutableData = [[cachedResponse data] mutableCopy];
NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly;
// ...
return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
data:mutableData
userInfo:mutableUserInfo
storagePolicy:storagePolicy];
}
如果 -connection:willCacheResponse: 返回 nil,回應將不會緩存。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
如果不實現此方法,NSURLConnection 就簡單地使用本來要傳入 -connection:willCacheResponse:的那個緩存對象,
所以除非你需要改變一些值或者阻止緩存,否則這個代理方法不必實現。
注意事項
正如它那個毫無關系但是名字相近的小伙伴 NSCache 一樣,NSURLCache 也是有一些特別的。在 iOS 5,磁盤緩存開始支持,
但僅支持 HTTP,非 HTTPS(iOS 6中增加了此支持)。Peter Steinberger關于這個主題寫了一篇優秀的文章
(http://petersteinberger.com/blog/2012/nsurlcache-uses-a-disk-cache-as-of-ios5/),
在深入研究內部細節后實現他自己的 NSURLCache 子類(https://github.com/steipete/SDURLCache)。
Daniel Pasco 在 Black Pixel 上的另一篇文章
http://blackpixel.com/blog/2012/05/caching-and-nsurlconnection.html
描述了一些與服務器通信時不設置緩存頭的意外的默認行為。
NSURLCache 提醒著我們熟悉我們正在操作的系統是多么地重要。開發 iOS 或 Mac OS X 程序時,
這些系統中的重中之重,非 URL Loading System莫屬。
無數開發者嘗試自己做一個簡陋而脆弱的系統來實現網絡緩存的功能,殊不知NSURLCache只要兩行代碼就能搞定且好上100倍。
甚至更多開發者根本不知道網絡緩存的好處,也從未嘗試過,導致他們的應用向服務器作了無數不必要的網絡請求。
所以如果你想看到世界的變化,你想確保你有程序總以正確的方式開啟,在
-application:didFinishLaunchingWithOptions:設置一個共享的 NSURLCache 吧。
本文譯者:candeladiao,原文:URL filtering for UIWebView on the iPhone
說明:譯者在做app開發時,因為頁面的javascript文件比較大導致加載速度很慢,所以想把javascript文件打包在app里,當UIWebView需要加載該腳本時就從app本地讀取,但UIWebView并不支持加載本地資源。最后從下文中找到了解決方法,第一次翻譯,難免有誤,大家多多指教。
iCab Mobile(一款iOS平臺的網頁瀏覽器)要實現一個攔截管理器來過濾頁面上的廣告及其他東西。它有一個簡單的基于URL過濾規則的列表(通常由用戶維護),當頁面包含的資源(圖片、js以及css等),文件的URL存在于規則列表中時,資源就不會被加載。
但看一下UIWebView類的API,會發現我們沒有辦法知道UIWebView正在加載什么資源,更糟的是,當你希望過濾掉某些資源文件的時候,沒有方法可以強制UIWebView不去加載這些文件,
攔截器看起來貌似沒有可能實現。
當然還是有解決方案的,否則這篇文件就沒什么卵用。
正如上面所說,實現攔截器不能靠UIWebView,因為UIWebView沒有提供任何有用的API。
對UIWebView的所有請求,要找到一個能中斷所有HTTP 請求的切入點,我們需要先了解一下Cocoa的URL Loading System,因為UIWebView是使用URL Loading System從web端取數據的。我們需要的切入點NSURLCache類就是URL Loading System的一部分。雖然目前iOS系統不會在磁盤上緩存任何數據(后面的iOS系統版本或許會有不同),因此在UIWebView開始加載前,NSURLCache管理的緩存數據通常為空,但UIWebView仍然會檢測所請求資源文件是否存在于緩存。所以我們需要做的只是繼承NSURLCache并重載其方法:
`- (NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request`
|
UIWebView請求所有資源時都會調用這個方法。因為我們只需要在這個方法里判斷請求的URL是否是我們想攔截的。如果是則創建一個沒有內容的假response,否則只需調用super方法即可。
如下是實現細節:
1.繼承NSURLCache:
FilteredWebCache.h:
`@interface FilteredWebCache : NSURLCache`
`{ `
`} `
`@end`
子類的主要代碼
FilteredWebCache.m:
`#import "FilteredWebCache.h"`
`#import "FilterManager.h"`
`@implementation FilteredWebCache`
`- (NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request`
`{`
`NSURL *url = [request URL];`
`BOOL blockURL = [[FilterMgr sharedFilterMgr] shouldBlockURL:url];`
`if` `(blockURL) {`
`NSURLResponse *response =`
`[[NSURLResponse alloc] initWithURL:url`
`MIMEType:@``"text/plain"`
`expectedContentLength:1`
`textEncodingName:nil];`
`NSCachedURLResponse *cachedResponse =`
`[[NSCachedURLResponse alloc] initWithResponse:response`
`data:[NSData dataWithBytes:``" "` `length:1]];`
`[``super` `storeCachedResponse:cachedResponse forRequest:request];`
`[cachedResponse release];`
`[response release];`
`}`
`return` `[``super` `cachedResponseForRequest:request];`
`}`
`@end`
首先判斷URL是否需攔截(判斷通過FilterManager類實現,類實現在此不列出)。如果需要,創建一個無內容的響應對象并把它存在cache中。有人可能會認為只需要返回假的響應對象就夠了,沒必要緩存它。但這樣會因響應對象被系統釋放而導致app crash。不知道為何為會這樣,可能是iOS的bug(Mac OS X 10.5.x也存在同樣問題,而10.4.x及更早的系統上沒有問題),也可能是URL Loading System內部類之間的依賴所致。所以我們先緩存響應對象。確保所有響應都是真實存在于緩存中,這也iOS希望的,最重要的是不會crash.
更新:因為假的響應是以大于0的大小來初始化的,看起來結緩存它也是必要的。
2.創建新的緩存:
接下來需要創建一個新的緩存并告訴iOS系統使用新的緩存代替默認的,這樣當URL Loading System檢測資源緩存時才會調用上面的代碼。這要在任意UIWebView開始加載頁面前做,顯然應該放在app啟動的時候:
`NSString *path = ...``// the path to the cache file`
`NSUInteger discCapacity = 10*1024*1024;`
`NSUInteger memoryCapacity = 512*1024;`
`FilteredWebCache *cache =`
`[[FilteredWebCache alloc] initWithMemoryCapacity: memoryCapacity`
`diskCapacity: discCapacity diskPath:path];`
`[NSURLCache setSharedURLCache:cache];`
`[cache release];`
這里需要提供一個緩存存儲路徑。緩存文件由NSURLCache對象自動生成,我們無需事先創建文件,但要定義緩存文件所存位置(必須是應用程序“沙盒”內,如“tmp”目錄或是“Document”目錄)
這就是實現UIWebView基于URL進行請求過濾的所有內容,看起來其實并不復雜
注:如果過濾規則在app運行過程中會改變,你需要從緩存中刪除假的響應。NSURLCache提供了刪除方法,所以這不是問題。如果過濾規則不會改變,則無需關心