首先,最近有個想法是讓UIWebview支持webp,那麼原生的webview引擎是不支持的,所以就有了如標題所寫的想法。其實不僅僅局限於為了讓其支持webp,如果能讓webview的圖片請求都走向自己實現的圖片庫,那麼可以實現很多很多黑科技(比如每個圖片加個app特定水印?或者說裁剪圖片使webview能有更好的性能表現?很多很多的功能,不一一列舉了)。
webview和普通的NSURLRequest都是用同一套cache策略麼,也就是說他們的cache共享嗎?
具體的cache策略是怎樣的,是怎樣產生cache的?如果webview請求過一個圖片,而在webview外再次請求同一個圖片,會用直接用cache麼?
系統原生提供的cache的穩定性?
兩者的確是公用cache的。
在實驗中,webview請求過一個圖片之後,用NSURLRequest請求同一個圖片會產生兩個結果。
在前者請求完成後的短時間內直接進行下次請求,會重新請求而不會拿cache,而過一段時間後請求,則會拿cache,個人猜測是處理cache需要時間。
關於系統提供的cache,os隨時有可能清空你的app cache,這是不可靠的!這也是為什麼SDWebImage默認會有自己的一套緩存策略的原因。
上述說的cache策略是建立在你正確理解並且用好的NSURLRequestCachePolicy的前提下的。為什麼這麼說呢?有些人會濫用NSURLRequestCachePolicy ,比如明明NSURLRequestReloadIgnoringLocalCacheData,卻說怎麼沒緩存,這就有點蛋疼了。關於cache策略可以看看官方文檔,比我說的清楚多了~
無論采用哪種NSURLRequestCachePolicy,在請求的時候都會cache。而在下次請求(不包括[webview reload]
,刷新會強制重新發請求)的時候,則會根據NSURLRequestCachePolicy的策略決定是否拿緩存。如果是NSURLRequestUseProtocolCachePolicy,還要根據對應的協議(http\https\ftp)的服務端提供的response來決定。
其實思路挺簡單的,在之前介紹hybrid app的時候,已經介紹了NSURLProtocol這個神器的作用。這裡其實也是用NSURLProtocol來達到目的。
想一想,無非就是攔截掉webview的請求,將本身在webview的URL Load System的請求抽出來,我們自己處理掉,然後組裝成response返回給webview。對於webview而言是完全透明的,它不關心中途的網絡請求是如何完成的,它只知道它發出了請求,並且收到response即可。
換句話說即我們自己處理整個請求的所有過程。
如下圖所示:
簡簡單單看一下原理是很簡單,但是就是因為這小小的動作我們可以做到很多原本沒法讓webview做到的事情。
支持webp
實現與自己實現的圖片庫一致的緩存策略
將圖片大小尺寸的URL拼接策略放在本地
支持webp:將圖片請求在自己實現的圖片庫中完成,然後轉成webview支持的data格式透傳回去即可。
緩存:我們都知道系統的緩存是不可靠的,很多開發者會采用自己的緩存策略,而webview的請求如果不進行干預是沒法做到走我們自己的緩存策略的,但是如文章所說,即可做到。
圖片尺寸優化:往往對於H5而言,如果前端開發者沒有注意圖片尺寸問題,在PC和手機上圖片都采用一個尺寸的話,這是十分不好的,手機屏幕小並不需要這麼大尺寸的圖。往往大多數的圖片CDN都是會支持裁剪的,而攔截圖片請求在URL上拼接size是一個解決方式。
還有許許多多的功能,比如開頭提到的水印,從我這個app打開的圖片都被我加上了本app的icon,是不是很屌。
這裡有個小插曲:在撸demo的時候,我本想讓demo中的SDWebImage請求同一個圖片,結果發現怎麼也走不到protocol中。搞了半天沒法只能去看文檔,發現如下:
NSURLSessionNSURLSession與NSURLConnection注冊NSURLProtocol的方式是不同的!
demo下載地址
+ (BOOL)canInitWithRequest:(NSURLRequest *)request { NSString *path = request.URL.path; // 只處理是圖片的URL請求 if ([path hasSuffix:@".jpg"] || [path hasSuffix:@".jpeg"] || [path hasSuffix:@".webp"]) { if ([NSURLProtocol propertyForKey:WebviewImageProtocolHandledKey inRequest:request]) { return NO; } return YES; } return NO; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } - (void)startLoading { // 將本地的緩存組裝成報文傳回去 NSData *data = [self imageDataWithURL:self.request.URL]; if (data) { NSString *mimeType = @"image/jpeg"; NSMutableDictionary *header = [[NSMutableDictionary alloc] initWithCapacity:2]; NSString *contentType = [mimeType stringByAppendingString:@";charset=UTF-8"]; header[@"Content-Type"] = contentType; header[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long) data.length]; NSHTTPURLResponse *httpResponse = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL statusCode:200 HTTPVersion:@"1.1" headerFields:header]; [self.client URLProtocol:self didReceiveResponse:httpResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [self.client URLProtocol:self didLoadData:data]; [self.client URLProtocolDidFinishLoading:self]; } else { // 這裡其實應該是圖片庫對於圖片請求的處理,每個人實現的圖片庫不一樣,所以我就直接以webview的方式請求了 NSMutableURLRequest *newRequest = [self.request mutableCopy]; newRequest.allHTTPHeaderFields = self.request.allHTTPHeaderFields; [NSURLProtocol setProperty:@YES forKey:WebviewImageProtocolHandledKey inRequest:newRequest]; self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self]; } } - (void)stopLoading { [self.connection cancel]; } - (NSData*)imageDataWithURL:(NSURL*)url { // 假裝這是一個緩存,這裡拋磚引玉下,大家可以自己實現自己的圖片緩存策略 if ([url.absoluteString isEqualToString:@"http://kuailejim.com/images/background-cover.jpg"]) { NSString *filePath = [[NSBundle mainBundle] pathForResource:@"background-cover" ofType:@"jpg"]; NSData *data = [NSData dataWithContentsOfFile:filePath]; return data; } return nil; }
由於每個開發團隊可能有自己的圖片處理庫,所以我這裡就只給出簡單的思路,大家可以自行按照自己的圖片庫替代上述有注釋的代碼。
可以很明顯的看出,圖片請求沒了,因為走了本地的緩存。
本文只給出了如何進行網絡請求替換,並且給出一個小demo,其實能做的遠遠不止這些。正如文中所說webp,圖片尺寸優化等等,如果你感興趣也可以自己撸一個demo玩玩~