對(duì)于目前UIWebView的離線(xiàn)緩存方式主要有如下幾種:
1.HTML5 , Manifest
最開(kāi)始我的想法是使用HTML5中的離線(xiàn)存儲(chǔ)功能,也就是分析Manifest文件來(lái)存儲(chǔ)和更新部分資源文件。但是經(jīng)過(guò)實(shí)踐發(fā)現(xiàn),UIWebView根本不支持HTML5,他只實(shí)現(xiàn)了Webkit中頁(yè)面渲染的那一部分。所以要實(shí)現(xiàn)緩存必須要另辟蹊徑。
2.NSURLCache
盡管在官方的說(shuō)明文檔里面說(shuō)到NSURLCache和NSCachedURLResponse可以用于緩存,但經(jīng)我測(cè)試好像僅僅只能用于加載本地某些資源文件,而且還有大小的限制(好像根據(jù)iphone的版本不同而不同,最小是25KB吧),比如圖片和JS代碼, 而對(duì)于整體的頁(yè)面無(wú)法進(jìn)行加載。而且經(jīng)過(guò)測(cè)試也沒(méi)有感覺(jué)加載速度有明顯的提高,我用的緩存策略是NSURLRequestReturnCacheDataElseLoad(可能是沒(méi)有讀取本地的緩存文件?),離線(xiàn)模式下也無(wú)法加載(可能是baseURL的關(guān)系?)
另外做一點(diǎn)引申,對(duì)于動(dòng)態(tài)獲取數(shù)據(jù)的頁(yè)面,我們不需要緩存的那些請(qǐng)求,只要過(guò)濾掉就可以了。
先新建一個(gè)文件,把所有不需要緩存的請(qǐng)求的URL寫(xiě)在一個(gè)文件里,就象HTML5的 Cache Manifest那樣。
然后需要使用緩存的時(shí)候讀取這個(gè)文件,并在重寫(xiě)的- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request這個(gè)方法內(nèi)對(duì)請(qǐng)求進(jìn)行判斷,如果是屬于這個(gè)文件內(nèi)的,比如web service的請(qǐng)求就直接返回,其他的就繼續(xù)處理。
3.ASIHTTPRequest,ASIDownloadCache和 ASIWebPageRequest
首先我得說(shuō),這確實(shí)是個(gè)很好的框架,使用起來(lái)確實(shí)很方便,但是對(duì)于緩存這個(gè)問(wèn)題,好像也跟第二點(diǎn)提到的效果差不多,加載速度沒(méi)有明顯的提升,離線(xiàn)模式下也無(wú)法加載。這是實(shí)現(xiàn)的代碼:
4.NSURLProtocol
由于UIWebView無(wú)法實(shí)現(xiàn)離線(xiàn)緩存,因此想利用Archieve機(jī)制來(lái)實(shí)現(xiàn)文件形式的離線(xiàn)緩存機(jī)制。同時(shí),由于NSURLRequest每一次對(duì)鏈接的請(qǐng)求,都將觸發(fā)NSURLProtocol的回調(diào),因此對(duì)NSURLProtocol合理應(yīng)用可以很好的達(dá)到離線(xiàn)緩存的目的。
一、NSURLProtocol與NSURLProtocolClient簡(jiǎn)介:
首先,我先介紹一下NSURLProtocol與NSURLProtocolClient:
NSURLProtocol是一組方法,其中蘋(píng)果文檔是這樣描述的:
NSURLProtocol is an abstract class which provides the basic structure for performing protocol-specific loading of URL data.
它是一個(gè)抽象類(lèi),為載入U(xiǎn)RL的data的一些特定協(xié)議提供基礎(chǔ)的結(jié)構(gòu)。要實(shí)現(xiàn)它里面的函數(shù)就必須繼承它,因此小Potti將在后面創(chuàng)建一個(gè)MWURLProtocol類(lèi)繼承它,并實(shí)現(xiàn)它其中的一系列函數(shù)。
而NSURLProtocol其中有個(gè)成員就是NSURLProtocolClient的一個(gè)實(shí)例。因?yàn)镹SURLProtocol是由一系列的回調(diào)函數(shù)構(gòu)成的(注冊(cè)函數(shù)除外),而要對(duì)URL的data進(jìn)行各種操作時(shí)就到了調(diào)用NSURLProtocolClient實(shí)例的時(shí)候了,這就實(shí)現(xiàn)了一個(gè)鉤子,去操作URL data。
NSURLProtocol有以下一系列的回調(diào)方法:
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id)client;
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
- (void)startLoading;
- (void)stopLoading;
其中canInitWithRequest是詢(xún)問(wèn)是否處理該請(qǐng)求的回調(diào),如果不處理則后面所有函數(shù)都不會(huì)再調(diào)用。startLoading和stopLoading是分別對(duì)于loading開(kāi)始從網(wǎng)頁(yè)上抓取數(shù)據(jù),從網(wǎng)頁(yè)上抓取完數(shù)據(jù)的回調(diào)。其中startLoading稱(chēng)為我們可以重點(diǎn)利用的函數(shù)。
NSURLProtocolClient主要有以下方法:
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
其中wasRedirectedToRequest是重定向函數(shù),cachedResponseIsValid是對(duì)cached的操作,didReceiveResponse是受到Response時(shí)的調(diào)用處理函數(shù),didLoadData是load完數(shù)據(jù)時(shí)的調(diào)用,而后面的大致也與函數(shù)題目意思一樣。而這些函數(shù)有個(gè)好處就是通通只需要我們調(diào)用它,系統(tǒng)就會(huì)做對(duì)應(yīng)的事情,重載它也可以,不過(guò)一般不用這么麻煩。
看到這些函數(shù)是不是想到了NSURLConnectionDataDelegate中的回調(diào)呢?哈哈,其實(shí)小Potti將在后面對(duì)2者有一個(gè)很好的結(jié)合。
NSURLProtocol離線(xiàn)緩存的要點(diǎn):
1、盡早注冊(cè)你的URLProtocol(application:didFinishLaunchingWithOptions:)。
2、NSURLProtocol是NSURLConnection的handler。NSURLConnection的每個(gè)請(qǐng)求都會(huì)去便利所有的Protocols,并詢(xún)問(wèn)你能處理這個(gè)請(qǐng)求么(canInitWithRequest:)。如果這個(gè)Protocol返回YES,則第一個(gè)返回YES的Protocol會(huì)來(lái)處理這個(gè)connection。Protocols的遍歷是反向的,也就是最后注冊(cè)的Protocol會(huì)被優(yōu)先判斷。
3、 當(dāng)你的handler被選中了,connection就會(huì)調(diào)用–>initWithRequest:cachedResponse:client:,緊接著會(huì)調(diào)用–>startLoading。然后你需要負(fù)責(zé)回調(diào):–>URLProtocol:didReceiveResponse:cacheStoragePolicy:,有些則會(huì)調(diào)用:–>URLProtocol:didLoadData:, 并且最終會(huì)調(diào)用–>URLProtocolDidFinishLoading:。你有沒(méi)有發(fā)現(xiàn)這些方法和NSURLConnectiondelegate的方法非常類(lèi)似——這絕非偶然!
4、當(dāng)online的情況下,RNCachingURLProtocol只是負(fù)責(zé)將請(qǐng)求轉(zhuǎn)發(fā)給一個(gè)新的NSURLConnection,并且拷貝一份結(jié)果給原來(lái)的connection。offline時(shí),RNCachingURLProtocol就會(huì)從磁盤(pán)里載入先前的結(jié)果,并將這些數(shù)據(jù)發(fā)回給連接。整個(gè)過(guò)程只有區(qū)區(qū)200行代碼(不包含Reachability)。
5、這里還有一個(gè)有趣的問(wèn)題,就是當(dāng)RNCachingURLProtocol創(chuàng)建了一個(gè)新的NSURLConnection的,即新的connection也會(huì)去找一個(gè)handler。如果RNCachingURLProtocol說(shuō)可以處理,那么就死循環(huán)了。怎么解決呢?通過(guò)添加自定義HTTP Header(X-RNCache)來(lái)標(biāo)記這個(gè)請(qǐng)求,告訴RNCachingURLProtocol不要再處理這個(gè)請(qǐng)求。
6、它可以響應(yīng)所有的connection,所以你可能需要修改canInitWithRequest:來(lái)選擇你要緩存的數(shù)據(jù)。
另外:并發(fā)請(qǐng)求或復(fù)雜網(wǎng)絡(luò)請(qǐng)求的緩存請(qǐng)使用MKNetworkKit(我們也在一個(gè)項(xiàng)目中使用了這個(gè)類(lèi)庫(kù),非常輕量快捷是ASI的很不錯(cuò)的替代品)。