WebViewJavascriptBridge 原理解析

WebViewJavascriptBridge應該是當前最流行最成功的OC與Web交互實現了。最近看了一下他的實現原理

我們可以在OC中調用javascript方法,但是反過來不能在javascript中調用OC方法。所以WebViewJavascriptBridge的實現過程就是在OC環境和javascript環境各自保存一個相互調用的信息。每一個調用之間都有id和callbackid來找到兩個環境對應的處理。


image.png

WebViewJavascriptBridge_JS.m文件中是javascript環境的bridge初始化和處理,里面負責接收oc發給javascript的消息,并且把javascript環境的消息發送給oc。
WebViewJavascriptBridge.m主要負責OC環境的消息處理,并且把OC環境的消息發送給javascript環境。
WebViewJavascriptBridgeBase.m主要實現了OC環境的bridge初始化和處理。
ExampleApp.html主要用于模擬生產環境下的web端。

初始化過程

1、OC環境初始化

我們從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交互的信息。

2、OC環境注冊方法

注冊一個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];
    }
    3、Web環境初始化

加載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;
    }
    3、WebViewJavascriptBridge_JS.js解析

上面我們講到了注入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發消息給WEB

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的流程走完了。

WEB發消息給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中的請求攔截、添加進度條、運營商劫持、如何組織交互規則等問題這里還沒有涉及。這些在我們項目中運用,具體就不抽出來了。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容