iOS WebView JavascriptBridgeの原理解析

29260 ワード

前言
WebView JavascriptBridgeのgithubアドレスWebViewJavascriptBridgeは、現在最も人気のある最も成功したOCWebとの相互作用によって実現されるべきである.OCjavascript方法を呼び出すことができますが、逆にjavascriptOC方法を呼び出すことはできません.したがって、WebViewJavascriptBridgeの実装プロセスは、OC環境およびjavascript環境においてそれぞれ1つの相互呼出し情報を保存することである.各呼び出しの間にidcallbackidがあり、2つの環境に対応する処理を見つける.
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;
}
- (id)init {
    if (self = [super init]) {
        self.messageHandlers = [NSMutableDictionary dictionary];
        self.startupMessageQueue = [NSMutableArray array];
        self.responseCallbacks = [NSMutableDictionary dictionary];
        _uniqueId = 0;
    }
    return self;
}
//message Handlers OC環境登録を保存するための方法であり、keyは方法名であり、valueはこの方法に対応するコールバックblock/startup Message Queを保存するためのものであり、正直なところ、javascirpt環境に送信する必要があるメッセージです.レスポンスCallbacksは、Javascript環境でOCが相互に起動するコールバックモジュールを保存するために使用されます._を通すuniqueIdにタイムスタンプを加えて、各呼び出しのコールバックを決定します.javascriptとの間の相互作用に関するすべての情報はmessageHandlersおよびresponseCallbacksに記憶されている.これらの2つの属性は、OC環境とjavascriptが相互作用する情報を記録している.
2、OC環境登録方法OC方法を登録し、OCJSに呼び出しを呼び出す方法を提供し、javascriptに保存する.
[_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環境初期化messageHandlers環境のWebをロードして、ここは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);
}
//ExampleAPP.htmlが実行するときに着信するパラメータは、一つの方法である.
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)
    })
};
//駆動全setupWebViewJavascriptBridgeの初期化
setupWebViewJavascriptBridge(callback);
hander関数を呼び出し、この関数が入ってきたsetupWebViewJavascriptBridgeも関数です.callback関数には、callback環境に登録されたjavascriptOCから提供される方法を呼び出す方法があります.JSの実装中に、最初の初期化でなければ、setupWebViewJavascriptBridgeまたはwindow.WebViewJavascriptBridgeの二つの判断によって戻ってくることがわかった.window.WVJBCallbacksは、iframeの中のウィンドウとして理解でき、webviewiframeの属性を変更すると、私たちのブラウザがリンクのジャンプを実現したに相当する.例えば、wwww.baidu.comからwwww.google.comにジャンプします.以下のコードの目的はsrcへのジャンプを実現することです.これにより、https://__bridge_loaded__環境を初期化するjavascriptの役割を果たす.
//このコードはbridgeのコードをロードする役割を果たすという意味です.
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);
WebViewJavascriptBridge_JS.jsがジャンプしている限り、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);
    }
}
このコードでは、まずwebviewを通じて、通常のジャンプか[_base isWebViewJavascriptBridgeURL:url]のジャンプかを判断します.webViewjavascriptBridegeが初期化__bridge_loaded__環境であることを示すメッセージであれば、javascriptであれば、__wvjb_queue_message__メッセージを送信することを示す.javascriptは明らかに第1のメッセージである.https://__bridge_loaded__具体的な判断論理コードは以下の通りである.
#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;
}
次にOC方法を呼び出すと、この方法は[_base injectJavascriptFile]の方法をWebViewJavascriptBridge_JS.jsに注入して実行し、webviewの環境を初期化するjavascriptの役割を果たす.
//        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、brige解析
上記ではWebViewJavascriptBridge_JS.js注入方法をjavascriptに説明しました.具体的なコードはwebviewというファイルの中の方法です.このファイルのコードを解析することにより、WebViewJavascriptBridge_JS.js環境のjavascriptがどのように初期化されたかを知ることができる.
;(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);
        }
    }
})();
実は、bridgeファイル全体が即座に実行されるjs方法であることを発見しました.
まず、javascriptオブジェクトを初期化することを発見した.また、このオブジェクトはWebViewJavascriptBridgeオブジェクトに与えられた値であり、ここでwindowオブジェクトはwindowとして理解される.したがって、webview環境において、OC方法を呼び出すなら、jsによって具体的な方法を加えて呼び出すことができる.window.WebViewJavascriptBridgeオブジェクトには、WebViewJavascriptBridgeの環境注入がjavascriptに呼び出される方法OCがあり、registerHandlerは、javascriptの環境方法のOCを呼び出す.callHandlerこの方法の役割は、_fetchQueue環境の方法シーケンスをjavascript文字列に変換し、JSON環境に再変換することである.OCは、_handleMessageFromObjCを処理してOCに環境を与える方法である.
このファイルにおいても、javascriptiframeジャンプ機能を実装するwebviewが初期化され、これにより、urlプロキシ方法の呼び出しが励起される.
    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);
上のwebviewsrcです.これはhttps://__wvjb_queue_message__/が送信したjavascriptの第1のメッセージであり、OC環境のOCと同様に、startupMessageQueue t環境初期化が完了したら、javascripjavascriptに送信するメッセージを直ちに送信する.
そして私たちは書類の一番後ろに次のコードがあります.このコードの役割は、OCにおけるExampleApp.html方法を直ちに実行することである.callbackに入力されたcallbackパラメータは、ここで初期化されたbridgeオブジェクトである.
    //  _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);
        }
    }
ここまで、window.WebViewJavascriptBridge環境およびOC環境のjavascriptは確立された.bridege及びOCの環境にはいずれもjavascriptオブジェクトがあり、このオブジェクトは登録された各方法及びコールバックを保持し、それぞれのメッセージキュー、コールバックbridgeidなどの一連の情報を保持している.requestIdOCにメッセージを送る.WEBは、OC環境を呼び出す方法であり、javascriptExampleApp.htmlに登録されている方法を呼び出す.
//        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];
}
すべての情報をbridge.registerHandlerという辞書に保存します.中にはパラメータmessage、コールバックdata、メッセージ名IDcallbackIdが埋め込まれています.具体的には以下の通りです
- (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];
}
handlerNameメッセージを順番に並べ、OC環境のフォーマットに変換する.次に、メインスレッドでjavascriptを呼び出します.
//      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:@"
" withString:@"\
"]; 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文字列は以下の通りです.
WebView Javascript Bridge.uhandleMessage FroomObjC('「calbackId」:「bjucbu 1」,「data」:「OC呼び出しJS方法」:「OC呼び出しJS方法のパラメータ」)、「handleName」:「OC呼び出しJS提供方法」
つまり、_evaluateJavascript環境におけるjavascriptオブジェクトのBridge方法によってである._handleMessageFromObjCに行って、WebViewJavascriptBridege_JS.jsの処理を見ます.
//   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);
            }
        }
    }
}
上記のコードは分かりやすいですが、メッセージに_handleMessageFromObjCがあれば、折り返しという意味です.直接呼び出しcallbackId方法は、OCに情報を返す.そうでなければ、_doSend環境アクティブコールWebの場合である.OCcallbackIDhandlerNameを一つのresponseCallbackオブジェクトにパッケージ化して保存します.messageを介してメッセージをOC環境に送信する.bridgeの具体的な実現を見てみましょう.
//    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;
}
その中で最も重要なのは、_doSendOCを変更することによって後の方である.これにより、_doSendのプロキシ方法iframeがトリガされ、messagingIframe.srcにおいてwebviewの環境によってトリガされるコールバックが処理される.具体的には以下の通りです
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);
}
ここでwebView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler方法を行きます.OCを呼び出してjavascriptに対するコールバック情報を取得する.
//  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];
    }];
}
[self WKFlushMessageQueue];からWebViewJavascriptBridge._fetchQueue()へのコールバックメッセージを取得した後、javascriptOCから戻った情報をjavascript環境のOCが識別できる情報に加工する.具体的な実装を見つけて実行します.
//  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からのレスポンスIdを通じて対応するWVJBRIesponseCallbackを取得します.このblockを実行します.ここでOCからjavascriptにメッセージを送り、javascriptはOCにメッセージを返すプロセスを終了しました.WEBからOCにメッセージを送ります
まずExampleAPP.htmlのbridge.callHandler方法を通じて、ここのbridgeはwindow.WebView JavascriptBridgeの対象です.
bridge.callHandler('OC     JS  ',params, function(response) {
    log('JS  OC    ', response)
})
次にWindows.WebView JavascriptBridgeのcalHanderメソッドを呼び出します.
//web     OC     
function callHandler(handlerName, data, responseCallback) {
    if (arguments.length == 2 && typeof data == 'function') {
        responseCallback = data;
        data = null;
    }
    _doSend({ handlerName: handlerName, data: data }, responseCallback);
}
そしてWebView JavascriptBridgeを呼び出します.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、calbackId、及び各idに対応する具体的な実現が維持されています.
OCはjavascript環境のwindow.WebView JavascriptBridgeオブジェクトを通じて具体的な方法を探して実行します.
javascriptは、iframeのsrcを変えてwebviewの代理方法webViewを出発します.webView decidepolicyForNavitionAction:(WKNavigations*)navitional Action decisionhandler:からこのメッセージを送信します.
実はここではwebviewとOCの橋渡し問題を解析しただけです.他のwebviewでの要求ブロッキング、プログレスバーの追加、オペレータのハイジャック、インタラクティブルールの整理などの問題はここではまだ触れていません.