UIWebView 已被全面廢棄,故本文只分析 WKWebView 實(shí)現(xiàn)。源碼見(jiàn) WebViewJavascriptBridge
先來(lái)看下在 WKWebView 下,是怎么使用JSBridge 的。
WKWebView* webView = [[NSClassFromString(@"WKWebView") alloc] initWithFrame:self.view.bounds];
webView.navigationDelegate = self;
[self.view addSubview:webView];
[WebViewJavascriptBridge enableLogging];
_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[_bridge setWebViewDelegate:self];
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}];
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
所以,看來(lái) JSBridge 對(duì)象是 WebViewJavascriptBridge 實(shí)現(xiàn)的。
WebViewJavascriptBridge.h宏定義如下
#if defined __MAC_OS_X_VERSION_MAX_ALLOWED
#define WVJB_PLATFORM_OSX
#define WVJB_WEBVIEW_TYPE WebView
#define WVJB_WEBVIEW_DELEGATE_TYPE NSObject<WebViewJavascriptBridgeBaseDelegate>
#define WVJB_WEBVIEW_DELEGATE_INTERFACE NSObject<WebViewJavascriptBridgeBaseDelegate, WebPolicyDelegate>
#elif defined __IPHONE_OS_VERSION_MAX_ALLOWED
#import <UIKit/UIWebView.h>
#define WVJB_PLATFORM_IOS
#define WVJB_WEBVIEW_TYPE UIWebView
#define WVJB_WEBVIEW_DELEGATE_TYPE NSObject<UIWebViewDelegate>
#define WVJB_WEBVIEW_DELEGATE_INTERFACE NSObject<UIWebViewDelegate, WebViewJavascriptBridgeBaseDelegate>
#endif
所以,在 iOS 上,其本質(zhì)是
NSObject<UIWebViewDelegate, WebViewJavascriptBridgeBaseDelegate>
那 WKWebView 也是這個(gè)類,也遵守 UIWebViewDelegate? 是否有點(diǎn)奇怪?
看其內(nèi)部實(shí)現(xiàn),就會(huì)發(fā)現(xiàn),當(dāng)系統(tǒng)支持 WebKit 時(shí),并且用戶傳的是 WKWebView 時(shí),實(shí)例化 WebViewJavascriptBridge 對(duì)象時(shí),就直接以 WKWebView 進(jìn)行了初始化。
+ (instancetype)bridge:(id)webView {
#if defined supportsWKWebView
if ([webView isKindOfClass:[WKWebView class]]) {
return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
}
#endif
if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
WebViewJavascriptBridge* bridge = [[self alloc] init];
[bridge _platformSpecificSetup:webView];
return bridge;
}
[NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
return nil;
}
所以,接下來(lái),我們就去看 WKWebViewJavascriptBridge
實(shí)例化對(duì)象
+ (instancetype)bridgeForWebView:(WKWebView*)webView {
WKWebViewJavascriptBridge* bridge = [[self alloc] init];
[bridge _setupInstance:webView];
[bridge reset];
return bridge;
}
- (void) _setupInstance:(WKWebView*)webView {
_webView = webView;
_webView.navigationDelegate = self;
_base = [[WebViewJavascriptBridgeBase alloc] init];
_base.delegate = self;
}
上述代碼又實(shí)例化了一個(gè)很重要成員變量 WebViewJavascriptBridgeBase
整個(gè) JSBridege objc 代碼的實(shí)現(xiàn)就集中在了 WKWebViewJavascriptBridge 和 WebViewJavascriptBridgeBase,此外還有一個(gè) JS 代碼的注入文件 WebViewJavascriptBridge_js,整個(gè)庫(kù)的核心可以說(shuō)就是這幾個(gè)文件了。
WebViewJavascriptBridge 加載

可以看到,
WebViewJavascriptBridge 加載過(guò)程主要如下:
- h5 網(wǎng)頁(yè)加載,內(nèi)部添加了一個(gè)不展示的 iframe,其 src 屬性設(shè)置為了
https://__bridge_loaded__
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
- iframe src 加載時(shí),其請(qǐng)求被
WKWebViewWKNavigationDelegate代理方法攔截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
- 內(nèi)部判斷為加載 WebViewJavascriptBridge 請(qǐng)求,執(zhí)行注入 js 代碼
WebViewJavascriptBridge_js,實(shí)現(xiàn) JSBridge,掛載在 window 上。
window.WebViewJavascriptBridge = {
registerHandler: registerHandler,
callHandler: callHandler,
disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
};
注入 js 同時(shí),又添加了一個(gè) iframe 元素,其 src 設(shè)置為了 https://__wvjb_queue_message__,注入的時(shí)候就會(huì)加載,然后被 WKNavigationDelegate 攔截。
如果 load web 之前,就調(diào)用了 callHandler,則會(huì)先存儲(chǔ)起來(lái),先執(zhí)行注入 WebViewJavascriptBridge_js,然后再去 sendData。
- (void)injectJavascriptFile {
NSString *js = WebViewJavascriptBridge_js();
[self _evaluateJavascript:js];
if (self.startupMessageQueue) {
NSArray* queue = self.startupMessageQueue;
self.startupMessageQueue = nil;
for (id queuedMessage in queue) {
[self _dispatchMessage:queuedMessage];
}
}
}
native 調(diào)用 h5

以 native 調(diào)用H5 testJavascriptHandler 方法為例, native 調(diào)用 h5 主要經(jīng)歷如下幾個(gè)步驟:
- h5 首先注冊(cè)
testJavascriptHandler - native 發(fā)送的數(shù)據(jù),被包裝成 json 字符串作為參數(shù),然后使用
WebViewJavascriptBridge._handleMessageFromObjC('%@');在 WKWebView 執(zhí)行 js 代碼。json 字符串包含:datacallbackIdhandlerName參數(shù)。同時(shí)將回調(diào)根據(jù)callbackId存儲(chǔ)起來(lái)。(callbackId使用objc_cb_+ 自增 id) - js 方法
_dispatchMessageFromObjC來(lái)處理數(shù)據(jù)。如果有callbackId,則會(huì)使用_doSend方法,將callbackId轉(zhuǎn)為responseId,加入testJavascriptHandler返回的responseData, 然后將數(shù)據(jù)存儲(chǔ)到sendMessageQueue對(duì)象中,然后加載messagingIframesrc,src 鏈接為https://__wvjb_queue_message__ -
WKNavigationDelegate代理攔截到 messagingIframe 重新加載 信息,執(zhí)行 js 代碼WebViewJavascriptBridge._fetchQueue();獲取存在sendMessageQueue中數(shù)據(jù)。 - 從
sendMessageQueue中獲取到responseId,根據(jù)responseId取到存儲(chǔ)在2 中存儲(chǔ)在responseCallbacks的 callback handler,然后 native 調(diào)用 handler,傳入 js 的responseData。
h5 調(diào)用 native

以 h5 調(diào)用 native
testObjcCallback handler 為例:
- native 需要先注冊(cè)
testObjcCallbackhandler - h5 調(diào)用 handler
testObjcCallback
bridge.callHandler('testObjcCallback', { 'foo': 'bar' }, function (response) {
log('JS got response', response)
})
js
_doSend()方法,處理調(diào)用,將handlerNamedatacallbackId包裝成JSON 字典對(duì)象, 存到sendMessageQueue中,并存儲(chǔ)回調(diào)(如有調(diào)用有回調(diào)),然后設(shè)置messagingIframe,讓其重新加載https://__wvjb_queue_message__。其中callbackId生成規(guī)則'cb_'+(uniqueId++)+'_'+new Date().getTime()WKNavigationDelegate 代理攔截
https://__wvjb_queue_message__,處理調(diào)用。首先通過(guò)WebViewJavascriptBridge._fetchQueue();獲取 js 數(shù)據(jù),即sendMessageQueue中數(shù)據(jù)。根據(jù)
sendMessageQueue中數(shù)據(jù),是否有callbackId。如果有callbackId, 即在 native handler 中,傳入responseData,并將callbackId轉(zhuǎn)為responseId, 然后再次WKWebView執(zhí)行 js 代碼。h5
WebViewJavascriptBridge中 通過(guò)_handleMessageFromObjC方法處理調(diào)用。拿到responseData和responseId, 根據(jù)responseId,找到3中存儲(chǔ)的回調(diào),然后執(zhí)行。