編寫(xiě)高性能的 iOS HybridApp

對(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ò)的替代品)。

demo:https://github.com/rnapier/RNCachingURLProtocol

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容