介紹
正常情況下當你需要寫一個具備網絡連接的iOS程序,你會想要一個本地的iOS接口能夠接收網絡上的所有數據。然而,在項目中總是有一些限制你可以實現的東西,而且有時候你可能想要爲用戶顯示一個規整的頁面。如果你打算採用這種方式,你最好確信網絡接口儘可能流暢。你可以採取的措施之一是將圖片的本地複本和其他非更新的資源包含到程序中。爲了在一個遠程加載的網頁中使用本地資源,或者需要遠程頁面以某種方式參考本地資源(例如通過URL主題),或者需要用本地地址來代替遠程地址。
在這個文章中,我將講述當網頁包含遠程資源時如何用本地資源來替代。
NSURLCache
在Mac上,你可以在WebViewDelegate上使用一系列不同的方式來實現,包括實現webView:resource:willSendRequest:redirectResponse:fromDataSource來使得NSURLRequest代替另一個。不幸的是,iOS中的UIWebViewDelegate並不如此好用因此我們需要以另外的方式來實現。幸運的是,還有一點你可以利用:就是NSURLCache在幾乎每個請求下都會被調用。
正常情況下,只有很少的數據存儲在NSURLCache中,特別是在更舊的iOS設備上,這個存儲區很小。即使你利用setMemoryCapacity:函數來增加這個緩存的大小,它相對於Mac上的NSURLCache來說還是太小了以至於不能存儲資源。
當然在這個例子中那不是問題,因爲我們將會子類化NSURLCache並且實現自定義的版本,該版本將保證可以存儲我們所需的資源而且不需要pre-caching(在程序運行之前所有的資源都要保證準備在存儲去內)。
cachedResponseForRequest:
唯一一個我們需要重寫的函數是cachedResponseForRequest:,這能夠允許我們在它發送前查看每一個請求而且如果我們需要的話返回本地數據。在這個代碼中,我會使用詞典來將遠程URL映射爲在本地程序相關庫中的資源的文件名。如果一個請求是指向特定的URL,那麼將返回本地文件內容。
下面給出了這個詞典。
- (NSDictionary *)substitutionPaths
{
return [NSDictionary dictionaryWithObjectsAndKeys:@"fakeGlobalNavBG.png",
@"http://images.apple.com/global/nav/images/globalnavbg.png",
nil];
}
只要針對URL:http://image.apple.com/global/nav/images/globalnavbg.png請求發出,那麼下面的cachedResponseForRequest:可以利用資源文件夾中的fakeGlobalNavBG.png文件來代替。
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
// Get the path for the request
NSString *pathString = [[request URL] absoluteString];
// 判斷我們是否爲這個路徑提供了替代資源
NSString *substitutionFileName = [[self substitutionPaths] objectForKey:pathString];
if (!substitutionFileName)
{
// 沒有替代資源,返回默認值
return [super cachedResponseForRequest:request];
}
// 如果我們已經創建了一個緩存實例,那麼返回它
NSCachedURLResponse *cachedResponse = [cachedResponses objectForKey:pathString];
if (cachedResponse)
{
return cachedResponse;
}
// 獲得替代文件的路徑
NSString *substitutionFilePath =
[[NSBundle mainBundle] pathForResource:[substitutionFileName stringByDeletingPathExtension]
ofType:[substitutionFileName pathExtension]];
NSAssert(substitutionFilePath, @"File %@ in substitutionPaths didn't exist", substitutionFileName);
// 加載替代數據
NSData *data = [NSData dataWithContentsOfFile:substitutionFilePath];
// 創建可緩存的響應
NSURLResponse *response = [[[NSURLResponse alloc] initWithURL:[request URL]
MIMEType:[self mimeTypeForPath:pathString]
expectedContentLength:[data length]
textEncodingName:nil] autorelease];
cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:response data:data] autorelease];
// 爲後續響應,把它加入我們的響應詞典中
if (!cachedResponses)
{
cachedResponses = [[NSMutableDictionary alloc] init];
}
[cachedResponses setObject:cachedResponse forKey:pathString];
return cachedResponse;
}
設置我們的緩存區作爲共享緩存
一個UIWebView試圖使用當前的+[NSURLCache sharedURLCache]。爲了調用我的代碼,你需要創建一個NSURLCache的子類並且調用+[NSURLCache setSharedURLCache:]。這裏需要注意:一旦你設置新的網絡緩存,你可能打算保持它工作直到你的程序退出。
當UIWebView向你的NSURLCache請求資源時,它假設NSURLCache具備NSCachedURLResponse。如果當UIWebView正在使用它的時候你釋放了NSCachedURLResponse,有可能你的程序會崩潰。不幸的是,迫使WebKit釋放它的參考(references)—在某些例子裏它何時釋放是不確定的。只有WebKit去調用removeCachedResponseForRequest:的時候它才通知你可以丟棄那些資源。
這意味着你必須保證程序中只有一個NSURLCache,在application:didFinishLaunchingWithOptions方法中進行設置並且不要移去它。
一個限制
顯然地,如果你設置了要用來存儲本地數據的緩存區,只有一個查看緩存區的請求才是使其生效。
這意味這如果URL請求是requestWithURL:cachePolicy:timeoutInterval:,緩存策略是NSURLRequestReloadIgnoringCacheData,那麼這個請求將忽略本地替代。
默認情況下,NSURLRequests的緩存策略是NSURLRequestUseProtocolCachePolicy。這個HTTP的緩存策略是相當複雜的而且我從來沒有見過一個正常的NSURLRequest忽視緩存,這些規則可能會在某些情況下產生它忽視緩存的情況。如果這些情況發生的話,你的程序應該保持正常工作。
本地替代緩存示例程序:LocalSubstitutionCache.zip
下面是程序截圖:
利用我們的NSURLCache子類調用了後,頂部灰色鏈接欄上的灰色鏈接按鈕被在本地資源文件中的藍色圖像所代替。
結論:
這個工作的意圖是允許UIWebView響應更靈敏而且更像本地用戶界面。
事實上,UIWebView決不會具有本地用戶界面那樣的集成度和靈敏的響應。但是
使得本地存儲儘可能多的資源有助於儘可能少的帶給用戶不好的體驗。