iOS UIWebView URL攔截
譯者在做app開(kāi)發(fā)時(shí),因?yàn)轫?yè)面的javascript文件比較大導(dǎo)致加載速度很慢,所以想把javascript文件打包在app里,當(dāng)UIWebView需要加載該腳本時(shí)就從app本地讀取,但UIWebView并不支持加載本地資源。***從下文中找到了解決方法,***次翻譯,難免有誤,大家多多指教。
iCab Mobile(一款iOS平臺(tái)的網(wǎng)頁(yè)瀏覽器)要實(shí)現(xiàn)一個(gè)攔截管理器來(lái)過(guò)濾頁(yè)面上的廣告及其他東西。它有一個(gè)簡(jiǎn)單的基于URL過(guò)濾規(guī)則的列表(通常由用戶(hù)維護(hù)),當(dāng)頁(yè)面包含的資源(圖片、js以及css等),文件的URL存在于規(guī)則列表中時(shí),資源就不會(huì)被加載。
但看一下UIWebView類(lèi)的API,會(huì)發(fā)現(xiàn)我們沒(méi)有辦法知道UIWebView正在加載什么資源,更糟的是,當(dāng)你希望過(guò)濾掉某些資源文件的時(shí)候,沒(méi)有方法可以強(qiáng)制UIWebView不去加載這些文件,
攔截器看起來(lái)貌似沒(méi)有可能實(shí)現(xiàn)。
當(dāng)然還是有解決方案的,否則這篇文件就沒(méi)什么卵用。
正如上面所說(shuō),實(shí)現(xiàn)攔截器不能靠UIWebView,因?yàn)閁IWebView沒(méi)有提供任何有用的API。
對(duì)UIWebView的所有請(qǐng)求,要找到一個(gè)能中斷所有HTTP 請(qǐng)求的切入點(diǎn),我們需要先了解一下Cocoa的URL Loading System,因?yàn)閁IWebView是使用URL Loading System從web端取數(shù)據(jù)的。我們需要的切入點(diǎn)NSURLCache類(lèi)就是URL Loading System的一部分。雖然目前iOS系統(tǒng)不會(huì)在磁盤(pán)上緩存任何數(shù)據(jù)(后面的iOS系統(tǒng)版本或許會(huì)有不同),因此在UIWebView開(kāi)始加載前,NSURLCache管理的緩存數(shù)據(jù)通常為空,但UIWebView仍然會(huì)檢測(cè)所請(qǐng)求資源文件是否存在于緩存。所以我們需要做的只是繼承NSURLCache并重載其方法:
- - (NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request
UIWebView請(qǐng)求所有資源時(shí)都會(huì)調(diào)用這個(gè)方法。因?yàn)槲覀冎恍枰谶@個(gè)方法里判斷請(qǐng)求的URL是否是我們想攔截的。如果是則創(chuàng)建一個(gè)沒(méi)有內(nèi)容的假response,否則只需調(diào)用super方法即可。
如下是實(shí)現(xiàn)細(xì)節(jié):
1.繼承NSURLCache:
FilteredWebCache.h:
- @interface FilteredWebCache : NSURLCache
- {
- }
- @end
子類(lèi)的主要代碼
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是否需攔截(判斷通過(guò)FilterManager類(lèi)實(shí)現(xiàn),類(lèi)實(shí)現(xiàn)在此不列出)。如果需要,創(chuàng)建一個(gè)無(wú)內(nèi)容的響應(yīng)對(duì)象并把它存在cache中。有人可能會(huì)認(rèn)為只需要返回假的響應(yīng)對(duì)象就夠了,沒(méi)必要緩存它。但這樣會(huì)因響應(yīng)對(duì)象被系統(tǒng)釋放而導(dǎo)致app crash。不知道為何為會(huì)這樣,可能是iOS的bug(Mac OS X 10.5.x也存在同樣問(wèn)題,而10.4.x及更早的系統(tǒng)上沒(méi)有問(wèn)題),也可能是URL Loading System內(nèi)部類(lèi)之間的依賴(lài)所致。所以我們先緩存響應(yīng)對(duì)象。確保所有響應(yīng)都是真實(shí)存在于緩存中,這也iOS希望的,最重要的是不會(huì)crash.
更新:因?yàn)榧俚捻憫?yīng)是以大于0的大小來(lái)初始化的,看起來(lái)結(jié)緩存它也是必要的。
2.創(chuàng)建新的緩存:
接下來(lái)需要?jiǎng)?chuàng)建一個(gè)新的緩存并告訴iOS系統(tǒng)使用新的緩存代替默認(rèn)的,這樣當(dāng)URL Loading System檢測(cè)資源緩存時(shí)才會(huì)調(diào)用上面的代碼。這要在任意UIWebView開(kāi)始加載頁(yè)面前做,顯然應(yīng)該放在app啟動(dòng)的時(shí)候:
- 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];
這里需要提供一個(gè)緩存存儲(chǔ)路徑。緩存文件由NSURLCache對(duì)象自動(dòng)生成,我們無(wú)需事先創(chuàng)建文件,但要定義緩存文件所存位置(必須是應(yīng)用程序“沙盒”內(nèi),如“tmp”目錄或是“Document”目錄)
這就是實(shí)現(xiàn)UIWebView基于URL進(jìn)行請(qǐng)求過(guò)濾的所有內(nèi)容,看起來(lái)其實(shí)并不復(fù)雜
注:如果過(guò)濾規(guī)則在app運(yùn)行過(guò)程中會(huì)改變,你需要從緩存中刪除假的響應(yīng)。NSURLCache提供了刪除方法,所以這不是問(wèn)題。如果過(guò)濾規(guī)則不會(huì)改變,則無(wú)需關(guān)心