NSURLProtocol對WKWebView的處理

  以前寫過一篇文章是關於基於NSURLProtocol作的DNS解析,其中對NSURLProtocol也有了簡單的介紹,咱們都知道他能夠攔截全部基於URL Loading System 中的請求,可是對於WKWebview裏面所發出的請求即便他是http/https 也無能爲力,先來簡單的瞭解下WKWebView.javascript

WKWebview

  iOS8之後,蘋果推出了新框架Webkit,提供了替換UIWebView的組件WKWebView。各類UIWebView的問題沒有了,速度更快了,佔用內存少了,一句話,WKWebView是App內部加載網頁的最佳選擇!咱們作開發最關係的是內存問題,基本上網上全部的資料都在說WKWebview的內存佔用會更少,可是到底少了多少我這邊作了下測試,一樣是加載163的首頁
java

使用UIWebView的內存

使用WKWebview的內存

從上圖看出內存大概能優化百分之八十左右,並且從網頁的滑動上也確實有所改善。這麼明顯的性能提高可是蘋果並無徹底放棄UIWebView也必定有他的道理,就拿本文要講的NSURLProtocol攔截請求來講,WKWebview的兼容並不UIWebView好,還須要開發者作一些操做。

WebKit源碼分析

  因爲WKWebview是基於webkit內核來作的,因此咱們在使用的時候須要導入一個這樣的東西。git

#import <WebKit/WebKit.h>複製代碼

經過這個咱們能夠猜到WKWebview中全部的請求以及一些邏輯確定走的都是webkit裏面的東西,因此他對於網頁的加載之之類的操做也不會走系統本省的URL Loading System,這麼說來他的請求不能被NSURLProtocol攔截也是理所固然的了。不過WKWebview是否真的和NSURLProtocol一點關係都沒有還須要去研究,幸虧webkit是開源的,github上很容易找到源碼(大小大概是1G多點的zip,花了我將近一天時間來看)。拉下代碼直接搜索NSURLProtocol,看看有沒有有關的信息
github

搜索結果

看來的確是有和NSURLProtocol有關係,後面經過斷點的調用棧中也找到了

+ [NSURLProtocol canInitWithRequest:]複製代碼

這樣的字樣,再經過網上查一些資料也證明了個人猜測,其實WKWebview在一開始時候是會調用到NSURLProtocol中的入口方法canInitWithRequest的,可是就沒有而後了,也就是說WKWebview是和NSURLProtocol有必定關聯,只是在NSURLProtocol的入口處返回NO因此致使NSURLProtocol不接管WKWebview的請求。咱們點進webkit源碼中的CustomProtocol能夠看到,總體的結構咱們都差很少,可是我注意到每一個CustomProtocol的入口函數都有這樣一個判斷:
web

入口函數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應該是私有的因此使用時候須要對字符串作下處理,用加密的方式或者其餘就能夠了,實測能夠過審覈的。個人文章簡書和掘金同步更新測試

相關文章
相關標籤/搜索