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)求被
WKWebView
WKNavigationDelegate
代理方法攔截
- (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 字符串包含:data
callbackId
handlerName
參數(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ì)象中苹祟,然后加載messagingIframe
src
,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è)
testObjcCallback
handler - h5 調(diào)用 handler
testObjcCallback
bridge.callHandler('testObjcCallback', { 'foo': 'bar' }, function (response) {
log('JS got response', response)
})
js
_doSend()
方法斤吐,處理調(diào)用搔涝,將handlerName
data
callbackId
包裝成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í)行靠柑。