
提及其原理、所有用過它的童鞋都會(huì)說(shuō)他在js和Native(原生)之間搭建了一個(gè)橋梁。通過這個(gè)橋、使他們相互通信。但具體怎么通信呢?這個(gè)橋如何工作?十有八九說(shuō)卻不清。
JSBridge的邏輯簡(jiǎn)而言之如下
-
我這人比較喜歡先貼結(jié)論、方便伸手黨(像我這種)。
oc和js相互調(diào)用的邏輯都是如此
(請(qǐng)忽略腦圖的指向、能連上就行。由左右向中間)

- 調(diào)用方時(shí)會(huì)生成一個(gè)id。
- 將調(diào)用的callback與id(callbackId)綁定備用。
- 再將方法名與id(callbackId)發(fā)給注冊(cè)方。
- 注冊(cè)方通過方法名、找出對(duì)應(yīng)的響應(yīng)方法(handler)。
- handler執(zhí)行完畢后通過id(responseId)找出調(diào)用方對(duì)應(yīng)的callback返回。
- responseId代表被調(diào)用方發(fā)起、callbackId代表調(diào)用方發(fā)起、值都相同。
注意有一點(diǎn)不同
- js是通過重定向通知oc處理邏輯。參數(shù)先存在js中、然后通過oc調(diào)用js中_fetchQueue方法被oc獲取。
- oc是通過直接調(diào)用_handleMessageFromObjC并且傳遞了參數(shù)通知js處理邏輯。
正文
WebViewJavascriptBridge的原理本質(zhì)上也是協(xié)議攔截。
- 這個(gè)庫(kù)、具體的用法我就不寫了、反正寫也是copy別的教學(xué)帖子。
而且、在JSCore以及WKWebView已經(jīng)極其成熟的當(dāng)下。WebViewJavascriptBridge用到的地方并不是那么多。 - 我比較關(guān)心的是他如何以注冊(cè)以及調(diào)用這種寫法來(lái)實(shí)現(xiàn)的協(xié)議攔截。
所以我也并不是每行源碼都貼出來(lái)、只是貼一些關(guān)鍵的功能性代碼 - 其實(shí)我以前沒用過JSBridge、15年入行的時(shí)候就已經(jīng)是JSCore普及的時(shí)代了。
- 從零開始一行一行讀、有興趣不妨一起。
-
隨手下了一個(gè)最新的、2017-12-19:當(dāng)前版本號(hào)6.0.2。
先看JS調(diào)用Native
-
Native中注冊(cè):
[self.bridge registerHandler:@"getUserId" handler:^(id data, WVJBResponseCallback responseCallback) { if (responseCallback) { // 反饋給JS responseCallback(@{@"userId": @"123456"}); } }];
沒什么問題、方法名、js傳進(jìn)來(lái)的ballback(參數(shù)、回調(diào)block)
繼續(xù)看.
#import "WebViewJavascriptBridge.h" - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler { _base.messageHandlers[handlerName] = [handler copy]; }
- _base:WebViewJavascriptBridge所持有的WebViewJavascriptBridgeBase(簡(jiǎn)稱base)對(duì)象。
- messageHandlers:字典。存儲(chǔ)了注冊(cè)的方法名、ballback。
然后、線索斷了。也就是說(shuō)、ios這邊主動(dòng)做的事情、已經(jīng)沒了。
就是在注冊(cè)的時(shí)候?qū)⒎椒?、block。存儲(chǔ)起來(lái)備用。
既然是備用、搜索這個(gè)函數(shù)messageHandlers、我們可以發(fā)現(xiàn)。
- 藍(lán)色部分:WebView已經(jīng)WKWebview的注冊(cè)事件、就是上面我們說(shuō)的那樣。
- 綠色部分:看寫法就知道是js文件。內(nèi)部確實(shí)也是js端的注冊(cè)方法。
- 紅色部分:沒錯(cuò)紅色部分就是剛才我們使用的這個(gè)字典、具體的用處了。
那繼續(xù)看紅色部分:
- (void)flushMessageQueue:(NSString *)messageQueueString;
#import "WebViewJavascriptBridgeBase.h" - (void)flushMessageQueue:(NSString *)messageQueueString{ //省略掉其他代碼之后 ...... WVJBHandler handler = self.messageHandlers[message[@"handlerName"]]; if (!handler) { NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message); continue; } handler(message[@"data"], responseCallback); }
- messageQueueString:字符串。本身的格式應(yīng)該大概是
"[{"handlerName":"getUserId","data":null,"callbackId":"cb_2_1513740848071"}]"是個(gè)字符串形的json、每部含有三個(gè)參數(shù)。除了callbackId、我們應(yīng)該都很好理解。
- message[@"data"]: 我們注冊(cè)時(shí)候的參數(shù)。
- responseCallback:顯而易見是我們注冊(cè)時(shí)候的回調(diào)函數(shù)。
那先來(lái)看看回調(diào)怎么傳遞給js的吧
//也是在該方法中、生成了這個(gè)responseCallback WVJBResponseCallback responseCallback = NULL; NSString* callbackId = message[@"callbackId"]; if (callbackId) { responseCallback = ^(id responseData) { if (responseData == nil) { responseData = [NSNull null]; } WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData }; [self _queueMessage:msg]; }; } else { responseCallback = ^(id ignoreResponseData) { // Do nothing };
在我們觸發(fā)回調(diào)的時(shí)候、我們r(jià)esponseCallback(@{@"userId": @"123456"});
其中@{@"userId": @"123456"}。就是這個(gè)responseData。
通過與callbackId關(guān)聯(lián)成一個(gè)json。調(diào)用_queueMessage方法處理。
注意。
這里callbackId已經(jīng)更名為responseId。
然后會(huì)再走入此大方法一次。進(jìn)入
if (responseId) { WVJBResponseCallback responseCallback = _responseCallbacks[responseId]; responseCallback(message[@"responseData"]); [self.responseCallbacks removeObjectForKey:responseId]; }
然后直接回調(diào)給js。這次的回調(diào)、才是真正的返還給js。
- (void)_queueMessage:(WVJBMessage*)message { if (self.startupMessageQueue) { [self.startupMessageQueue addObject:message]; } else { [self _dispatchMessage:message]; } } - (void)_dispatchMessage:(WVJBMessage*)message { NSString *messageJSON = [self _serializeMessage:message > pretty:NO]; [self _log:@"SEND" json:messageJSON]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; ******對(duì)json字符串進(jìn)行一系列格式化處理***** messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"]; NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON]; if ([[NSThread currentThread] isMainThread]) { [self _evaluateJavascript:javascriptCommand]; } else { dispatch_sync(dispatch_get_main_queue(), ^{ [self _evaluateJavascript:javascriptCommand]; }); } }
message:
{"responseId":"cb_3_1513741962583","responseData":{"userId":"123456"}};javascriptCommand:
WebViewJavascriptBridge._handleMessageFromObjC('{\"responseId\":\"cb_3_1513741962583\",\"responseData\":{\"userId\":\"123456\"}}');_evaluateJavascript:方法
底層是讓webview去注入這段js函數(shù)至于_handleMessageFromObjC的實(shí)現(xiàn)
就是屬于WebViewJavascriptBridge_js文件中的范疇了。一會(huì)從js端切入的時(shí)候再去看。
所以說(shuō)這段代碼、就是oc返回給js的回調(diào)函數(shù)無(wú)誤。
再回過頭來(lái)看看-(void)flushMessageQueue:(NSString *)messageQueueString;方法是如何被調(diào)用的
再次搜索、很明顯了、是攔截協(xié)議并且判斷復(fù)合要求之后直接調(diào)用的。沒什么太繞的東西。

簡(jiǎn)單的標(biāo)注了一下
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if (webView != _webView) { return YES; } NSURL *url = [request URL]; __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate; if ([_base isWebViewJavascriptBridgeURL:url]) { //js通過Bridge發(fā)起的url if ([_base isBridgeLoadedURL:url]) { //注入js(WebViewJavascriptBridge_js) [_base injectJavascriptFile]; } else if ([_base isQueueMessageURL:url]) { //js主動(dòng)調(diào)啟oc NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]; //去調(diào)用剛才分析的那個(gè)方法--(通過注冊(cè)的方法名、調(diào)用對(duì)應(yīng)的block) [_base flushMessageQueue:messageQueueString]; } else { //控制臺(tái)報(bào)錯(cuò) [_base logUnkownMessage:url]; } //攔截 return NO; } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { //正?;卣{(diào)給webView的VC return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; } else { return YES; } }
至此、OC注冊(cè)Handler時(shí)所做的事、結(jié)束。
- OC將方法名、block(參數(shù)、回調(diào))儲(chǔ)存到字典。
- OC接收到j(luò)s調(diào)用的url、將block取出。傳入data/callback并調(diào)用該block。
- OC在方法處理完畢時(shí)。通過js傳入的callbackId、以及我們的返回值作為參數(shù)、調(diào)用bridgejs文件中的_handleMessageFromObjC方法。將返回值callback給js中的指定ballback。
- 需要注意一點(diǎn)的是、JSBridge在發(fā)起請(qǐng)求的時(shí)候、并不是將參數(shù)、callbackId等直接作為url發(fā)送出來(lái)。而是直接請(qǐng)求https://wvjb_queue_message/(這一點(diǎn)應(yīng)該算是其中蠻出彩的地方了。很多人也只是知道其使用的是協(xié)議攔截)
- 參數(shù)通過bridgejs生成、并且獲取。具體這一步如何實(shí)現(xiàn)、下面分析js中調(diào)用Native的時(shí)候再來(lái)看(因?yàn)楝F(xiàn)在我也沒呢~)。
-
js中調(diào)用Native注冊(cè)的方法:
//app.html bridge.callHandler('getUserId','參數(shù)不需要的話可以省略不謝',function(response){ log(response.userId) }) //WebViewJavascriptBridge_JS function callHandler(handlerName, data, responseCallback) { if (arguments.length == 2 && typeof data == 'function') { responseCallback = data; data = null; } _doSend({ handlerName:handlerName, data:data }, responseCallback); }
進(jìn)行了一些參數(shù)處理(js中很多都會(huì)根據(jù)傳入?yún)?shù)數(shù)量的不同、內(nèi)部進(jìn)行進(jìn)一步處理)、處理結(jié)束直接丟給_doSend函數(shù)
function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message['callbackId'] = callbackId; } sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
這里我們看到了一個(gè)很熟悉的參名字callbackId
- 就是說(shuō)js的callback函數(shù)在這里會(huì)被保存起來(lái)。以callbackId為鍵保存在responseCallbacks這個(gè)字典中、將來(lái)可以根據(jù)callbackId獲取、完成回調(diào)。
- callbackId也作為新的參數(shù)、添加進(jìn)了message字典中。
ok、線索又?jǐn)嗔?。剩下一個(gè)sendMessageQueue以及messagingIframe
- messagingIframe:
這個(gè)應(yīng)該比較容易理解。iframe是一個(gè)內(nèi)嵌的網(wǎng)頁(yè)標(biāo)簽。你既然修改了對(duì)應(yīng)的src(鏈接)、webView自然會(huì)收到一個(gè)重定向的請(qǐng)求。
- sendMessageQueue
既然修改了iframe的src、讓webVIew攔截了協(xié)議。sendMessageQueue自然就是為了提供參數(shù)而存在的了。
具體、我們來(lái)找找看(搜索sendMessageQueue)。
//WebViewJavascriptBridge_JS function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString; } //#import "WebViewJavascriptBridgeBase.h" - (NSString *)webViewJavascriptFetchQueyCommand { return @"WebViewJavascriptBridge._fetchQueue();"; } - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { ***省略*** //js主動(dòng)調(diào)啟oc NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]]; //去調(diào)用剛才分析的那個(gè)方法--(通過注冊(cè)的方法名、調(diào)用對(duì)應(yīng)的block) [_base flushMessageQueue:messageQueueString]; ***省略*** }
- _fetchQueue負(fù)責(zé)提供剛才封裝的Message(含有callbackID那個(gè))
- webViewJavascriptFetchQueyCommand負(fù)責(zé)在oc中注入js。調(diào)用_fetchQueue
- webView重定向時(shí)、調(diào)用webViewJavascriptFetchQueyCommand獲取參數(shù)、并且傳遞給flushMessageQueue去執(zhí)行oc中注冊(cè)方法的block。
這不是完事了么...
對(duì)啊、這就完事了。注冊(cè)-調(diào)用-回調(diào)、一個(gè)閉環(huán)。具體可以翻回去再看一遍、會(huì)恍然大悟。決定畫個(gè)圖。圖已經(jīng)放在最上面了
OC調(diào)用JS
先看js文件吧、還是想先從注冊(cè)看起。
既然我們是iOS開發(fā)、js這邊就從配置環(huán)境開始看代碼吧。畢竟很多人還是會(huì)很好奇js中的bridge實(shí)例從哪來(lái)的。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
setupWebViewJavascriptBridge(function(bridge) {
<!-- 操作bridge -->
}
這段呢、是我從網(wǎng)上copy來(lái)的?;舅薪虒W(xué)帖子都這么用。
通過調(diào)用setupWebViewJavascriptBridge方法、并傳入一個(gè)callback函數(shù)、來(lái)獲取bridge對(duì)象。
-
啥是callback?
其實(shí)就是我們block或者閉包、block本質(zhì)上也就是個(gè)代碼塊而已。只不過js不愛整那么多花花事罷了。
因?yàn)檫@個(gè)bridge對(duì)象是在加載完我們iOS本地的bridge_js文件之后才會(huì)生成。生成完丟進(jìn)callBack還給你。
-
何時(shí)加載的bridge_js文件?
之前我們分析代碼的時(shí)候已經(jīng)提到了、iframe修改src會(huì)觸發(fā)webView的代理。方法中第四行的WVJBIframe對(duì)象、就是觸發(fā)加載bridge_js(wvjbscheme://__ BRIDGE_LOADED __)的iframe。
-
何時(shí)返回的bridge對(duì)象?
方法中的1-3行??赡芸粗悬c(diǎn)別扭、我們可以調(diào)整一下順序。
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge);
return ;
}
if (window.WVJBCallbacks) {
window.WVJBCallbacks.push(callback);
return;
}
window.WVJBCallbacks = [callback];
WebViewJavascriptBridge對(duì)象是在bridge_js內(nèi)部被定義以及實(shí)現(xiàn)的。
就是說(shuō):
1、如果有WebViewJavascriptBridge直接返回。
2、否則每次調(diào)用時(shí)將callback放入數(shù)組。等生成了bridge、再遍歷返回。
初始化就到這、繼續(xù)看js中注冊(cè)方法的代碼。
bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) { var responseData = { 'Javascript Says':'Right back atcha!' }; if(responseCallback) { responseCallback(responseData); }; });
感覺和oc注冊(cè)方法的代碼一樣(其實(shí)注冊(cè)和調(diào)用、兩端的方法樣式都是相同的)。
接著看內(nèi)部、其實(shí)這邊的實(shí)現(xiàn)邏輯。也和oc一樣。
function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; }注冊(cè)字典@{方法名:handler函數(shù)};
搜索messageHandlers
function _dispatchMessageFromObjC(messageJSON) { var handler = messageHandlers[message.handlerName]; if (!handler) { console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message); } else { handler(message.data, responseCallback); } }
- 根據(jù)message.handlerName取出對(duì)應(yīng)的handler、然后把responseCallback丟進(jìn)去執(zhí)行。
- responseCallback哪來(lái)的?和OC中的實(shí)現(xiàn)一樣
將callbackId、responseData一同返還給oc的回調(diào)block。
繼續(xù)往回找
function _handleMessageFromObjC(messageJSON) { _dispatchMessageFromObjC(messageJSON); }
-_handleMessageFromObjC:
這就很眼熟了。之前我們看到這里、然后說(shuō)留到j(luò)s這邊分析。
現(xiàn)在想想他做了什么?
拿到OC發(fā)來(lái)的messageJSON。里面有responseId/handlerName以及responseData。然后通過responseId將js中對(duì)應(yīng)的callback調(diào)起/執(zhí)行指定已經(jīng)注冊(cè)函數(shù)。
然后、最后兩個(gè)方法。
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName { //封裝message@{callbackId/handlerName/data} [self _queueMessage:message]; } - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback { [_base sendData:data responseCallback:responseCallback handlerName:handlerName]; }
callHandler發(fā)起調(diào)用、sendData發(fā)送數(shù)據(jù)。和js調(diào)用oc的時(shí)候簡(jiǎn)直一模一樣。
這不是又完事了么...
嗯、本來(lái)以為調(diào)用的方式不一樣。不過現(xiàn)在看來(lái)和js調(diào)用oc的方式基本相同。圖也就不畫了、直接去最上面看就行了。
最后、如果你也讀了源碼。肯定對(duì)responseId的存在表示疑問。
- responseId以及callbackId互斥。注冊(cè)方發(fā)起指向調(diào)用方、后者表示調(diào)用方發(fā)起指向被調(diào)用方。
- 相應(yīng)的。handlerName也只是在存在callbackId的時(shí)候才存在、并且執(zhí)行handler。因?yàn)槿绻嬖趓esponseId、那個(gè)responseCallback就會(huì)直接被執(zhí)行、完成回調(diào)、不會(huì)繼續(xù)向下了。
最后
本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏、萬(wàn)望留言斧正。如果不吝賜教小弟更加感謝。
