NSURLProtocol對WKWebView的處理

茉莉兒發表於2017-06-21

  之前寫過一篇文章是關於基於NSURLProtocol做的DNS解析,其中對NSURLProtocol也有了簡單的介紹,我們都知道他可以攔截所有基於URL Loading System 中的請求,但是對於WKWebview裡面所發出的請求即使他是http/https 也無能為力,先來簡單的瞭解下WKWebView.

WKWebview

  iOS8以後,蘋果推出了新框架Webkit,提供了替換UIWebView的元件WKWebView。各種UIWebView的問題沒有了,速度更快了,佔用記憶體少了,一句話,WKWebView是App內部載入網頁的最佳選擇!我們做開發最關係的是記憶體問題,基本上網上所有的資料都在說WKWebview的記憶體佔用會更少,但是到底少了多少我這邊做了下測試,同樣是載入163的首頁

NSURLProtocol對WKWebView的處理
使用UIWebView的記憶體

NSURLProtocol對WKWebView的處理
使用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對WKWebView的處理
搜尋結果

看來的確是有和NSURLProtocol有關係,後面通過斷點的呼叫棧中也找到了

+ [NSURLProtocol canInitWithRequest:]複製程式碼

這樣的字樣,再通過網上查一些資料也證實了我的猜想,其實WKWebview在一開始時候是會呼叫到NSURLProtocol中的入口方法canInitWithRequest的,但是就沒有然後了,也就是說WKWebview是和NSURLProtocol有一定關聯,只是在NSURLProtocol的入口處返回NO所以導致NSURLProtocol不接管WKWebview的請求。我們點進webkit原始碼中的CustomProtocol可以看到,整體的結構我們都差不多,但是我注意到每個CustomProtocol的入口函式都有這樣一個判斷:

NSURLProtocol對WKWebView的處理
入口函式1

NSURLProtocol對WKWebView的處理
入口函式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之後在呼叫該方法應該就可以了。通過他的原始碼也進一步印證了我的猜想(他也是這麼寫的)

NSURLProtocol對WKWebView的處理
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"];
    }複製程式碼

  實現效果。我將網頁中所有的圖片替換成了柴犬圖片

NSURLProtocol對WKWebView的處理
效果

#####值得注意
  因為WKBrowsingContextController和registerSchemeForCustomProtocol應該是私有的所以使用時候需要對字串做下處理,用加密的方式或者其他就可以了,實測可以過稽核的。我的文章簡書和掘金同步更新

相關文章