標籤:是你 rip 私人api origin UI 註冊 handler gem 結構
iOS8以後,蘋果推出了新架構Webkit,提供了替換UIWebView的組件WKWebView。各種UIWebView的問題沒有了,速度更快了,佔用記憶體少了,一句話,WKWebView是App內部載入網頁的最佳選擇!我們做開發最關係的是記憶體問題,基本上網上所有的資料都在說WKWebview的記憶體佔用會更少,但是到底少了多少我這邊做了下測試,同樣是載入163的首頁
使用UIWebView的記憶體 使用WKWebview的記憶體
從看出記憶體大概能最佳化百分之八十左右,而且從網頁的滑動上也確實有所改善。這麼明顯的效能提升但是蘋果並沒有完全放棄UIWebView也一定有他的道理,就拿本文要講的NSURLProtocol攔截請求來說,WKWebview的相容並不UIWebView好,還需要開發人員做一些操作。
WebKit源碼分析
由於WKWebview是基於webkit核心來做的,所以我們在使用的時候需要匯入一個這樣的東西。
#import <WebKit/WebKit.h>
通過這個我們可以猜到WKWebview中所有的請求以及一些邏輯肯定走的都是webkit裡面的東西,所以他對於網頁的載入之之類的操作也不會走系統本省的URL Loading System,這麼說來他的請求不能被NSURLProtocol攔截也是理所當然的了。不過WKWebview是否真的和NSURLProtocol一點關係都沒有還需要去研究,幸好webkit是開源的,github上很容易找到源碼(大小大概是1G多點的zip,花了我將近一天時間來看)。拉下代碼直接搜尋NSURLProtocol,看看有沒有有關的資訊
搜尋結果
看來的確是有和NSURLProtocol有關係,後面通過斷點的調用棧中也找到了
+ [NSURLProtocol canInitWithRequest:]
這樣的字樣,再通過網上查一些資料也證實了我的猜想,其實WKWebview在一開始時候是會調用到NSURLProtocol中的入口方法canInitWithRequest的,但是就沒有然後了,也就是說WKWebview是和NSURLProtocol有一定關聯,只是在NSURLProtocol的入口處返回NO所以導致NSURLProtocol不接管WKWebview的請求。我們點進webkit源碼中的CustomProtocol可以看到,整體的結構我們都差不多,但是我注意到每個CustomProtocol的入口函數都有這樣一個判斷:
入口函數1
入口函數2
(粉色的可以暫時認定為是它內部的一個custom字串)通過這個可以猜想,WKWebview並不是不走NSURLProtocol,而是需要滿足他的一個規則,他才會在入口函數這裡返回YES來給你允許存取,這個規則便是你所請求的URL的Scheme要和它內部配置的CustomScheme相同。不過這裡有一個疑問,蘋果在使用webkit時候為什麼會把http/https這樣福士化的scheme過濾掉,看來他是不建議開發人員來使用NSURLProtocol。接下來我們來看這個CustomScheme,既然蘋果內部規定好的那麼一定能通過某種方式來註冊一個自己的scheme,實在不行就hook嘛。通過翻他的源碼發現最終都指向一句代碼
[WKBrowsingContextController registerSchemeForCustomProtocol:testScheme];
方法實現為
+ (void)registerSchemeForCustomProtocol:(NSString *)scheme{WebProcessPool::registerGlobalURLSchemeAsHavingCustomProtocolHandlers(scheme);}void WebProcessPool::registerGlobalURLSchemeAsHavingCustomProtocolHandlers(const String& urlScheme){ if (!urlScheme) return; globalURLSchemesWithCustomProtocolHandlers().add(urlScheme); for (auto* processPool : allProcessPools()) processPool->registerSchemeForCustomProtocol(urlScheme);}
通過方法名字可以看出這個就是那個向webkit註冊CustomScheme的方法,只要我們在註冊完我們自己的CustomProtocol之後在調用該方法應該就可以了。通過他的源碼也進一步印證了我的猜想(他也是這麼寫的)
webkit源碼具體實施
找到了方法就要去實施,不過因為registerSchemeForCustomProtocol是WKBrowsingContextController的類方法,所以只能用WKBrowsingContextController去調用,但是在webkit的標頭檔發現WKBrowsingContextController並沒有開放出來,所以我們採用NSClassFromString和NSSelectorFromString方法來拿到類和對應的方法,整體代碼如下
//註冊自己的protocol [NSURLProtocol registerClass:[CustomProtocol class]]; //建立WKWebview WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init]; WKWebView * wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:config]; [wkWebView loadRequest:webViewReq]; [self.view addSubview:wkWebView]; //註冊scheme Class cls = NSClassFromString(@"WKBrowsingContextController"); SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:"); if ([cls respondsToSelector:sel]) { // 通過http和https的請求,同理可通過其他的Scheme 但是要滿足ULR Loading System [cls performSelector:sel withObject:@"http"]; [cls performSelector:sel withObject:@"https"]; }
值得注意
因為WKBrowsingContextController和registerSchemeForCustomProtocol應該是私人的所以使用時候需要對字串做下處理,用加密的方式或者其他就可以了,實測可以過審核的。
第三方的庫
GitHub:NSURLProtocol-WebKitSupport
iOS 讓 WKWebView 支援 NSURLProtocol