咱們的項目是一個OC與javascript重度交互的app,OC與javascript交互的那部分是在WebViewJavascriptBridge的github地址的基礎上修改的,WebViewJavascriptBridge應該是當前最流行最成功的OC與Web交互實現了。最近看了一下他的實現原理,順便也爲後面項目擴展打下基礎。
爲了簡化講解過程,我忽略了UIWebView的實現過程,只解析WKWebView的實現過程。javascript
咱們能夠在OC中調用javascript方法,可是反過來不能在javascript中調用OC方法。因此WebViewJavascriptBridge
的實現過程就是在OC環境和javascript環境各自保存一個相互調用的信息。每個調用之間都有id和callbackid來找到兩個環境對應的處理。下圖是我對於每一個類的講解:html
nouse文件夾下面的文件是與UIWebView相關的東西,咱們暫時無論,基本原理和WKWebView同樣。其中WebViewJavascriptBridge_JS.m
中是javascript代碼,爲了方便理解,我直接新建了一個WebViewJavascriptBridge_JS.js
文件來代替,方便後面解析。java
WebViewJavascriptBridge_JS.js
文件中是javascript環境的bridge初始化和處理,裏面負責接收oc發給javascript的消息,而且把javascript環境的消息發送給oc。git
WKWebViewJavascriptBridge.m
主要負責OC環境的消息處理,而且把OC環境的消息發送給javascript環境。github
WebViewJavascriptBridgeBase.m
主要實現了OC環境的bridge初始化和處理。web
ExampleApp.html
主要用於模擬生產環境下的web端。json
咱們從OC環境的初始化開始。瀏覽器
//初始化一個OC環境的橋WKWebViewJavascriptBridge而且初始化。 + (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; } //messageHandlers用於保存OC環境註冊的方法,key是方法名,value是這個方法對應的回調block //startupMessageQueue用於保存是實話過程當中須要發送給javascirpt環境的消息。 //responseCallbacks用於保存OC於javascript環境相互調用的回調模塊。經過_uniqueId加上時間戳來肯定每一個調用的回調。 - (id)init { if (self = [super init]) { self.messageHandlers = [NSMutableDictionary dictionary]; self.startupMessageQueue = [NSMutableArray array]; self.responseCallbacks = [NSMutableDictionary dictionary]; _uniqueId = 0; } return self; }
全部與javascript之間交互的信息都存儲在messageHandlers
和responseCallbacks
中。這兩個屬性記錄了OC環境與javascript交互的信息。app
註冊一個OC方法OC提供方法給JS調用
給javascript調用,而且把他的回調實現保存在messageHandlers
中。框架
[_bridge registerHandler:@"OC提供方法給JS調用" handler:^(id data, WVJBResponseCallback responseCallback) { //NSLog(@"testObjcCallback called: %@", data); responseCallback(@"OC發給JS的返回值"); }]; - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler { _base.messageHandlers[handlerName] = [handler copy]; }
加載Web環境的html,這裏就是ExampleAPP.html
文件,我刪除了非關鍵部分。
function setupWebViewJavascriptBridge(callback) { //第一次調用這個方法的時候,爲false if (window.WebViewJavascriptBridge) { var result = callback(WebViewJavascriptBridge); return result; } //第一次調用的時候,也是false if (window.WVJBCallbacks) { var result = window.WVJBCallbacks.push(callback); return result; } //把callback對象賦值給對象。 window.WVJBCallbacks = [callback]; //這段代碼的意思就是執行加載WebViewJavascriptBridge_JS.js中代碼的做用 var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0); } //setupWebViewJavascriptBridge執行的時候傳入的參數,這是一個方法。 function callback(bridge) { var uniqueId = 1 //把WEB中要註冊的方法註冊到bridge裏面 bridge.registerHandler('OC調用JS提供的方法', function(data, responseCallback) { log('OC調用JS方法成功', data) var responseData = { 'JS給OC調用的回調':'回調值!' } log('OC調用JS的返回值', responseData) responseCallback(responseData) }) }; //驅動全部hander的初始化 setupWebViewJavascriptBridge(callback);
咱們調用setupWebViewJavascriptBridge
函數,而且這個函數傳入的callback也是一個函數。callback函數中有咱們在javascript環境中註冊的OC調用JS提供的方法
方法。setupWebViewJavascriptBridge
的實現過程當中咱們能夠發現,若是不是第一次初始化,會經過window.WebViewJavascriptBridge
或者window.WVJBCallbacks
兩個判斷返回。
iframe能夠理解爲webview中的窗口,當咱們改變iframe的src屬性的時候,至關於咱們瀏覽器實現了連接的跳轉。好比從www.baidu.com
跳轉到www.google.com
。下面這段代碼的目的就是實現一個到https://__bridge_loaded__
的跳轉。從而達到初始化javascript環境的bridge的做用。
//這段代碼的意思就是執行加載WebViewJavascriptBridge_JS.js中代碼的做用 var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0);
咱們知道只要webview有跳轉,就會調用webview的代理方法。咱們重點看下面這個代理方法。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { if (webView != _webView) { return; } NSURL *url = navigationAction.request.URL; NSLog(@"點開URL%@",url); __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate; //若是是WebViewJavascriptBridge發送或者接受的消息,則特殊處理。不然按照正常流程處理。 if ([_base isWebViewJavascriptBridgeURL:url]) { //1第一次注入JS代碼 if ([_base isBridgeLoadedURL:url]) { [_base injectJavascriptFile]; //處理WEB發過來的消息 } else if ([_base isQueueMessageURL:url]) { [self WKFlushMessageQueue]; } else { [_base logUnkownMessage:url]; } decisionHandler(WKNavigationActionPolicyCancel); } //下面是webview的正常代理執行流程,不用管。 if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) { [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler]; } else { decisionHandler(WKNavigationActionPolicyAllow); } }
在這段代碼中,咱們首先經過[_base isWebViewJavascriptBridgeURL:url]
來判斷是不是普通的跳轉仍是webViewjavascriptBridege
的跳轉。若是是__bridge_loaded__
表示是初始化javascript環境的消息,若是是__wvjb_queue_message__
則表示是發送javascript消息。https://__bridge_loaded__
顯然是第一種消息。OC具體具體判斷邏輯代碼以下:
#define kOldProtocolScheme @"wvjbscheme" #define kNewProtocolScheme @"https" #define kQueueHasMessage @"__wvjb_queue_message__" #define kBridgeLoaded @"__bridge_loaded__" //是不是WebViewJavascriptBridge框架相關的連接 - (BOOL)isWebViewJavascriptBridgeURL:(NSURL*)url { if (![self isSchemeMatch:url]) { return NO; } BOOL result = [self isBridgeLoadedURL:url] || [self isQueueMessageURL:url]; return result; } /* 是不是WebViewJavascriptBridge發送或者接受的消息 */ - (BOOL)isSchemeMatch:(NSURL*)url { NSString* scheme = url.scheme.lowercaseString; BOOL result = [scheme isEqualToString:kNewProtocolScheme] || [scheme isEqualToString:kOldProtocolScheme]; return result; } //是WebViewJavascriptBridge發送的消息仍是WebViewJavascriptBridge的初始化消息。 - (BOOL)isQueueMessageURL:(NSURL*)url { NSString* host = url.host.lowercaseString; return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage]; } //是不是https://__bridge_loaded__這種初始化加載消息 - (BOOL)isBridgeLoadedURL:(NSURL*)url { NSString* host = url.host.lowercaseString; BOOL result = [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded]; return result; }
接下來調用[_base injectJavascriptFile]
方法,這個方法的做用就是把WebViewJavascriptBridge_JS.js
中的方法注入到webview中而且執行,從而達到初始化javascript環境的brige的做用。
//初始化的是否注入WebViewJavascriptBridge_JS.js - (void)injectJavascriptFile { NSString *js; //WebViewJavascriptBridge_JS.js文件內容其實就是WebViewJavascriptBridge_JS.m對應的內容,我只是把它整理方便閱讀。 if (true) { js = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WebViewJavascriptBridge_JS.js" ofType:nil] encoding:NSUTF8StringEncoding error:nil]; }else{ js = WebViewJavascriptBridge_js(); } //把javascript代碼注入webview中執行,這裏執行具體的注入操做。 [self _evaluateJavascript:js]; //若是javascript環境初始化完成之後,有startupMessageQueue消息。則當即發送消息。 if (self.startupMessageQueue) { NSArray* queue = self.startupMessageQueue; self.startupMessageQueue = nil; for (id queuedMessage in queue) { [self _dispatchMessage:queuedMessage]; } } } //把javascript代碼寫入webview - (NSString*) _evaluateJavascript:(NSString*)javascriptCommand { [_webView evaluateJavaScript:javascriptCommand completionHandler:nil]; return NULL; }
上面咱們講到了注入javascript方法到webview中。具體的代碼就是WebViewJavascriptBridge_JS.js
這個文件中的方法。咱們經過分析這個文件的代碼能夠知道javascript環境的bridge是如何初始化的。
;(function() { //若是已經初始化了,則返回。 if (window.WebViewJavascriptBridge) { return; } if (!window.onerror) { window.onerror = function(msg, url, line) { console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line); } } //初始化一些屬性。 var messagingIframe; //用於存儲消息列表 var sendMessageQueue = []; //用於存儲消息 var messageHandlers = {}; //經過下面兩個協議組合來肯定是不是特定的消息,而後攔擊。 var CUSTOM_PROTOCOL_SCHEME = 'https'; var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__'; //oc調用js的回調 var responseCallbacks = {}; //消息對應的id var uniqueId = 1; //是否設置消息超時 var dispatchMessagesWithTimeoutSafety = true; //web端註冊一個消息方法 function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; } //web端調用一個OC註冊的消息 function callHandler(handlerName, data, responseCallback) { if (arguments.length == 2 && typeof data == 'function') { responseCallback = data; data = null; } _doSend({ handlerName: handlerName, data: data }, responseCallback); } function disableJavscriptAlertBoxSafetyTimeout() { dispatchMessagesWithTimeoutSafety = false; } //把消息轉換成JSON字符串返回 function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString; } //OC調用JS的入口方法 function _handleMessageFromObjC(messageJSON) { _dispatchMessageFromObjC(messageJSON); } //初始化橋接對象,OC能夠經過WebViewJavascriptBridge來調用JS裏面的各類方法。 window.WebViewJavascriptBridge = { registerHandler: registerHandler, callHandler: callHandler, disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout, _fetchQueue: _fetchQueue, _handleMessageFromObjC: _handleMessageFromObjC }; //處理從OC返回的消息。 function _dispatchMessageFromObjC(messageJSON) { if (dispatchMessagesWithTimeoutSafety) { setTimeout(_doDispatchMessageFromObjC); } else { _doDispatchMessageFromObjC(); } function _doDispatchMessageFromObjC() { var message = JSON.parse(messageJSON); var messageHandler; var responseCallback; //回調 if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else {//主動調用 if (message.callbackId) { var callbackResponseId = message.callbackId; responseCallback = function(responseData) { _doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData }); }; } //獲取JS註冊的函數 var handler = messageHandlers[message.handlerName]; if (!handler) { console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message); } else { //調用JS中的對應函數處理 handler(message.data, responseCallback); } } } } //把消息從JS發送到OC,執行具體的發送操做。 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); //存儲消息的回調ID responseCallbacks[callbackId] = responseCallback; //把消息對應的回調ID和消息一塊兒發送,以供消息返回之後使用。 message['callbackId'] = callbackId; } //把消息放入消息列表 sendMessageQueue.push(message); //下面這句話會出發JS對OC的調用 //讓webview執行跳轉操做,從而能夠在 //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中攔截到JS發給OC的消息 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; } messagingIframe = document.createElement('iframe'); messagingIframe.style.display = 'none'; //messagingIframe.body.style.backgroundColor="#0000ff"; messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; document.documentElement.appendChild(messagingIframe); //註冊_disableJavascriptAlertBoxSafetyTimeout方法,讓OC能夠關閉回調超時,默認是開啓的。 registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout); //執行_callWVJBCallbacks方法 setTimeout(_callWVJBCallbacks, 0); //初始化WEB中註冊的方法。這個方法會把WEB中的hander註冊到bridge中。 //下面的代碼其實就是執行WEB中的callback函數。 function _callWVJBCallbacks() { var callbacks = window.WVJBCallbacks; delete window.WVJBCallbacks; for (var i = 0; i < callbacks.length; i++) { callbacks[i](WebViewJavascriptBridge); } } })();
其實咱們發現整個js文件就是一個當即執行的javascript方法。
首先咱們發現會初始化一個WebViewJavascriptBridge對象。而且這個對象是賦值給window對象,這裏window對象能夠理解爲webview。因此說咱們後面在OC環境中若是要調用js方法,就能夠經過window.WebViewJavascriptBridge
在加上具體方法來調用。
WebViewJavascriptBridge對象中有javascript環境注入的提供給OC調用的方法registerHandler,javascript調用OC環境方法的callHandler。
_fetchQueue這個方法的做用就是把javascript環境的方法序列化成JSON字符串,而後傳入OC環境再轉換。
_handleMessageFromObjC就是處理OC發給javascript環境的方法。
在這個文件中也初始化了一個iframe實現webview的url跳轉功能,從而激發webview代理方法的調用。
messagingIframe = document.createElement('iframe'); messagingIframe.style.display = 'none'; //messagingIframe.body.style.backgroundColor="#0000ff"; messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; document.documentElement.appendChild(messagingIframe);
上面的src就是https://__wvjb_queue_message__/
。這個是javascript發送的OC的第一條消息,目的和上面OC環境的startupMessageQueue同樣,就是在javascript環境初始化完成之後,把javascript要發送給OC的消息當即發送出去。
而後咱們看文件的最後面有以下代碼。這段代碼的做用就是當即執行ExampleApp.html中的callback方法。callback中傳入的bridge參數就是咱們這裏初始化的window.WebViewJavascriptBridge對象。
//執行_callWVJBCallbacks方法 setTimeout(_callWVJBCallbacks, 0); //初始化WEB中註冊的方法。這個方法會把WEB中的hander註冊到bridge中。 //下面的代碼其實就是執行WEB中的callback函數。 function _callWVJBCallbacks() { var callbacks = window.WVJBCallbacks; delete window.WVJBCallbacks; for (var i = 0; i < callbacks.length; i++) { callbacks[i](WebViewJavascriptBridge); } }
直到這裏,OC環境和javascript環境的bridege都創建完畢。OC和javascript環境都有一個bridge對象,這個對象都保存着註冊的每一個方法和回調,而且維護着各自的消息隊列、回調id、requestId等一系列信息。
OC要調用javascript環境的方法,其實就是調用ExampleApp.html
中的bridge.registerHandler
註冊的方法。
//點擊按鈕開始一個OC消息.ExampleWKWebViewController.m中一個方法開始。 - (void)callHandler:(id)sender { id data = @{ @"OC調用JS方法": @"OC調用JS方法的參數" }; [_bridge callHandler:@"OC調用JS提供的方法" data:data responseCallback:^(id response) { // NSLog(@"testJavascriptHandler responded: %@", response); }]; } /* handerName:OC調用JS提供的方法 data:{@"OC調用JS方法的參數":@"OC調用JS方法"} responseCallback:回調block */ - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback { [_base sendData:data responseCallback:responseCallback handlerName:handlerName]; }
把全部信息存入一個名字爲message的字典中。裏面拼裝好參數data
、回調IDcallbackId
、消息名字handlerName
。具體以下:
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName { NSMutableDictionary* message = [NSMutableDictionary dictionary]; if (data) { message[@"data"] = data; } if (responseCallback) { NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId]; self.responseCallbacks[callbackId] = [responseCallback copy]; message[@"callbackId"] = callbackId; } if (handlerName) { message[@"handlerName"] = handlerName; } [self _queueMessage:message]; }
把OC消息序列化、而且轉化爲javascript環境的格式。而後在主線程中調用_evaluateJavascript。
//把消息發送給WEB環境 - (void)_dispatchMessage:(WVJBMessage*)message { NSString *messageJSON = [self _serializeMessage:message pretty:NO]; [self _log:@"SEND" json:messageJSON]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"]; messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"]; 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]; }); } }
具體注入的javascript字符串以下:
WebViewJavascriptBridge._handleMessageFromObjC('{\"callbackId\":\"objc_cb_1\",\"data\":{\"OC調用JS方法\":\"OC調用JS方法的參數\"},\"handlerName\":\"OC調用JS提供的方法\"}');
其實就是經過javascript環境中的Bridge對象的_handleMessageFromObjC
方法。下面咱們去WebViewJavascriptBridege_JS.js
中看_handleMessageFromObjC
的處理過程。
//處理從OC返回的消息。 function _dispatchMessageFromObjC(messageJSON) { if (dispatchMessagesWithTimeoutSafety) { setTimeout(_doDispatchMessageFromObjC); } else { _doDispatchMessageFromObjC(); } function _doDispatchMessageFromObjC() { var message = JSON.parse(messageJSON); var messageHandler; var responseCallback; //回調 if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else {//主動調用 if (message.callbackId) { var callbackResponseId = message.callbackId; responseCallback = function(responseData) { _doSend({ handlerName: message.handlerName, responseId: callbackResponseId, responseData: responseData }); }; } //獲取JS註冊的函數 var handler = messageHandlers[message.handlerName]; if (!handler) { console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message); } else { //調用JS中的對應函數處理 handler(message.data, responseCallback); } } } }
上面這段代碼很容易理解,其實就是若是消息中有callbackId則表示是一個回調。直接調用_doSend方法把信息返回OC。不然就是Web環境主動調用OC的狀況。此時把callbackID、handlerName、responseCallback封裝進一個message對象中保存起來(其實你會發現和OC環境的bridge處理同樣)。而後經過_doSend發消息發送到OC環境。下面咱們看看_doSend的具體實現:
//把消息從JS發送到OC,執行具體的發送操做。 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); //存儲消息的回調ID responseCallbacks[callbackId] = responseCallback; //把消息對應的回調ID和消息一塊兒發送,以供消息返回之後使用。 message['callbackId'] = callbackId; } //把消息放入消息列表 sendMessageQueue.push(message); //下面這句話會出發JS對OC的調用 //讓webview執行跳轉操做,從而能夠在 //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中攔截到JS發給OC的消息 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
其中最重要仍是最後面的經過改變iframe的messagingIframe.src
。從而觸發webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
從而在OC中處理javascript環境觸發過來的回調。具體以下:
if ([_base isWebViewJavascriptBridgeURL:url]) { //第一次注入JS代碼 if ([_base isBridgeLoadedURL:url]) { [_base injectJavascriptFile]; //處理WEB發過來的消息 } else if ([_base isQueueMessageURL:url]) { [self WKFlushMessageQueue]; } else { [_base logUnkownMessage:url]; } decisionHandler(WKNavigationActionPolicyCancel); }
這裏會走[self WKFlushMessageQueue];
方法。而後經過調用WebViewJavascriptBridge._fetchQueue()
來獲取javascript給OC的回調信息。
//獲取WEB消息的JSON字符串 - (NSString *)webViewJavascriptFetchQueyCommand { return @"WebViewJavascriptBridge._fetchQueue();"; } ////把消息或者WEB回調從OC發送到OC - (void)WKFlushMessageQueue { NSString *js = [_base webViewJavascriptFetchQueyCommand]; [_webView evaluateJavaScript:js completionHandler:^(NSString* result, NSError* error) { if (error != nil) { NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error); } //把消息或者WEB回調從OC發送到OC [_base flushMessageQueue:result]; }]; }
獲取到javascript給OC的回調消息之後,而後把javascript的bridge返回的信息加工處理成OC環境的bridge能識別的信息。從而找到具體的實現執行。
//把從WEB發送的消息返回。而後在這裏處理 - (void)flushMessageQueue:(NSString *)messageQueueString{ if (messageQueueString == nil || messageQueueString.length == 0) { NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page."); return; } id messages = [self _deserializeMessageJSON:messageQueueString]; for (WVJBMessage* message in messages) { if (![message isKindOfClass:[WVJBMessage class]]) { NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message); continue; } [self _log:@"RCVD" json:message]; NSString* responseId = message[@"responseId"]; if (responseId) { WVJBResponseCallback responseCallback = _responseCallbacks[responseId]; responseCallback(message[@"responseData"]); [self.responseCallbacks removeObjectForKey:responseId]; } else { 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 }; } WVJBHandler handler = self.messageHandlers[message[@"handlerName"]]; if (!handler) { NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message); continue; } handler(message[@"data"], responseCallback); } } }
這裏會調用handler方法,經過javascript傳過來的responseId獲取對應的WVJBResponseCallback
。而後執行這個block。到這裏從OC發送消息到javascript而且javascript返回消息給OC的流程走完了。
首先經過ExampleAPP.html
中的bridge.callHandler
方法,這裏的bridge就是window.WebViewJavascriptBridge
對象:
bridge.callHandler('OC提供方法給JS調用',params, function(response) { log('JS調用OC的返回值', response) })
接下來調用window.WebViewJavascriptBridge
中的callHander方法
//web端調用一個OC註冊的消息 function callHandler(handlerName, data, responseCallback) { if (arguments.length == 2 && typeof data == 'function') { responseCallback = data; data = null; } _doSend({ handlerName: handlerName, data: data }, responseCallback); }
而後調用WebViewJavascriptBridge_JS.js
中的方法執行具體的操做。具體就和OC調用javascript過程同樣了,就不解釋了。
//把消息從JS發送到OC,執行具體的發送操做。 function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); //存儲消息的回調ID responseCallbacks[callbackId] = responseCallback; //把消息對應的回調ID和消息一塊兒發送,以供消息返回之後使用。 message['callbackId'] = callbackId; } //把消息放入消息列表 sendMessageQueue.push(message); //下面這句話會出發JS對OC的調用 //讓webview執行跳轉操做,從而能夠在 //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中攔截到JS發給OC的消息 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
其實如今想一想,原理很簡單。
分別在OC環境和javascript環境都保存一個bridge對象,裏面維持着requestId,callbackId,以及每一個id對應的具體實現。
OC經過javascript環境的window.WebViewJavascriptBridge
對象來找到具體的方法,而後執行。
javascript經過改變iframe的src來出發webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
從而實現把javascript消息發送給OC這個功能。
其實這裏只是解析了webview與OC交互的橋接問題,其餘好比webview中的請求攔截、添加進度條、運營商劫持、如何組織交互規則等問題這裏尚未涉及。這些在咱們項目中運用,具體就不抽出來了。
最後,具體的源碼在github地址。