1. 程式人生 > >iOS 讓 WKWebView 支持 NSURLProtocol

iOS 讓 WKWebView 支持 NSURLProtocol

建議 sys 占用內存 file ash 關聯 contain pre size

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"];
    }

值得註意
  • 關於私有API

因為WKBrowsingContextController和registerSchemeForCustomProtocol應該是私有的所以使用時候需要對字符串做下處理,用加密的方式或者其他就可以了,實測可以過審核的。

第三方的庫

GitHub:NSURLProtocol-WebKitSupport

iOS 讓 WKWebView 支持 NSURLProtocol