iOS WebViewJavascriptBridge源碼解析

一. 概述

做客戶端開發(fā)免不了要與WebView打交道,特別是對于Hybrid App,在H5所占比重越來越大的背景下,一套好的WebView 與原生交互的API顯得尤為重要,當然目前兩端都有比較成熟的三方庫進行支持。比如Android端的JsBridge,iOS端的WebViewJavascriptBridge,但是對于其內(nèi)部原理筆者一直一知半解,導致有時面對問題無從下手,最后決心分析WebViewJavascriptBridge的內(nèi)部實現(xiàn)原理,一是提升自己的源碼閱讀水平,其次也希望對以后的工作有所幫助。

二. 基本原理

pic

下載WebViewJavascriptBridge的源碼后可以看到其文件并不多,分別對幾個文件做簡單的介紹,后面詳細分析其源碼

  • WebViewJavascriptBridge_JS: JS橋接文件,通過它實現(xiàn)JS環(huán)境的初始化,里面就一個C函數(shù),返回的是JS方法。原生調(diào)用的JS方法與對應(yīng)的方法回調(diào)都需要先在這里面進行注冊。
  • WKWebViewJavascriptBridgeWebViewJavascriptBridge: WKWebViewUIWebView對應(yīng)的橋接文件。JS調(diào)用的原生方法與對應(yīng)的方法回調(diào)都需要先在這里面進行注冊。
  • WebViewJavascriptBridgeBase: 橋接基礎(chǔ)文件。通過他實現(xiàn)對原生環(huán)境的初始化,以及對方法存儲容器的初始化,當然還有對WebViewJavascriptBridge_JS里面JS方法的調(diào)用。

三. 源碼解析

大體了解了上面幾個類的作用,我們通過源碼來分析其內(nèi)部的實現(xiàn)邏輯。我們就以WebViewJavascriptBridgeDemo為例。

1. JS調(diào)用OC方法

(1) OC環(huán)境初始化與方法注冊

如何實現(xiàn)JS調(diào)用OC方法呢,首先要對當前OC環(huán)境進行初始化

// ExampleWKWebViewController
_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [_bridge setWebViewDelegate:self];
    
    [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];

....

// WKWebViewJavascriptBridge
+ (instancetype)bridgeForWebView:(WKWebView*)webView {
    WKWebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _setupInstance:webView];
    [bridge reset];
    return bridge;
}

....
// WKWebViewJavascriptBridge
- (void) _setupInstance:(WKWebView*)webView {
    _webView = webView;
    _webView.navigationDelegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    _base.delegate = self;
}
  • [WebViewJavascriptBridge bridgeForWebView:webView];: 看這個方法的調(diào)用棧,可以清晰的看到其作用是初始化 WKWebViewJavascriptBridge,進而實例化其對應(yīng)的WebViewJavascriptBridgeBase,還有綁定各自的代理,最終實現(xiàn)初始化OC調(diào)用環(huán)境的目的。

  • - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler; : 如果要實現(xiàn)JS調(diào)用原生方法的目的,那么必須對原生方法進行注冊,這個就是對應(yīng)的注冊方法。我們來看他內(nèi)部做了什么:

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

很簡單,只不過把當前的Block保存進了messageHandlers這個字典中,以便等JS端調(diào)用時,通過方法名稱來找到其對應(yīng)的實現(xiàn)。

(2) JS環(huán)境初始化與方法觸發(fā)

OC環(huán)境初始化與方法注冊完成后,我們來下JS環(huán)境的初始化
Demo中通過- (void)loadExamplePage:(WKWebView*)webView 方法加載網(wǎng)頁到當前的webView,來看下ExampleApp.html中的核心方法:

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 = 'https://__bridge_loaded__';
      document.documentElement.appendChild(WVJBIframe);
      setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
  }

  setupWebViewJavascriptBridge(function(bridge) {
  var uniqueId = 1
  function log(message, data) {
    var log = document.getElementById('log')
    var el = document.createElement('div')
    el.className = 'logLine'
    el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
    if (log.children.length) { log.insertBefore(el, log.children[0]) }
    else { log.appendChild(el) }
  }

  bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
    log('ObjC called testJavascriptHandler with', data)
    var responseData = { 'Javascript Says':'Right back atcha!' }
    log('JS responding with', responseData)
    responseCallback(responseData)
  })

  document.body.appendChild(document.createElement('br'))

  var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
  callbackButton.innerHTML = 'Fire testObjcCallback'
  callbackButton.onclick = function(e) {
    e.preventDefault()
    log('JS calling handler "testObjcCallback"')
    bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
      log('JS got response', response)
    })
  }
})
  • setupWebViewJavascriptBridge(callback)是核心方法,webView加載html后會首先調(diào)用這個方法。這個方法需要一個參數(shù)callback,也是一個函數(shù)。我們來看這個方法:
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 = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

第一次加載網(wǎng)頁時 window.WebViewJavascriptBridgewindow.WVJBCallbacks都是false,把window.WVJBCallbacks賦值為包含callback的數(shù)組,此時callback為一個函數(shù),就是后面的function(bridge) ....,接下來創(chuàng)建WVJBIframe,你可以把它理解為一個空白頁面,創(chuàng)建它的目的是設(shè)置src = 'https://__bridge_loaded__';,

注意這個src屬性很關(guān)鍵,當我們設(shè)置一個網(wǎng)頁的src屬性時,這個鏈接會被我們OC端的webView所捕獲,從而調(diào)用webView的代理方法- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler,

后面兩句代碼的的意思是加載當前空白頁,以便觸發(fā)OC的代理方法,然后立馬移除。

  • 接下來我們?nèi)?code>WKWebViewJavascriptBridge中看 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 這個代理方法攔截到請求后做了什么。
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

if ([_base isWebViewJavascriptBridgeURL:url]) {
    if ([_base isBridgeLoadedURL:url]) {
        [_base injectJavascriptFile];
    } else if ([_base isQueueMessageURL:url]) {
        [self WKFlushMessageQueue];
    } else {
        [_base logUnkownMessage:url];
    }
    decisionHandler(WKNavigationActionPolicyCancel);
    return;
}

首先判斷當前的URL是否是__wvjb_queue_message__或者__bridge_loaded__,剛才觸發(fā)的URL是 __bridge_loaded__
會調(diào)用WebViewJavascriptBridgeBase- (void)injectJavascriptFile方法。

- (void)injectJavascriptFile {
    // 獲取JS字符串
    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];
        }
    }
}

....

- (void) _evaluateJavascript:(NSString *)javascriptCommand {
    [self.delegate _evaluateJavascript:javascriptCommand];
}

....

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {
    [_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
    return NULL;
}

通過以上方法調(diào)用可以看到,最后是把WebViewJavascriptBridge_js(); JS方法字符串,通過方法 [_webView evaluateJavaScript:javascriptCommand completionHandler:nil] 注入到了webView中并且執(zhí)行。從而達到初始化javascript環(huán)境的brige的作用。

  • WebViewJavascriptBridge_js()方法解析
window.WebViewJavascriptBridge = {
  registerHandler: registerHandler,
  callHandler: callHandler,
  disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
  _fetchQueue: _fetchQueue,
  _handleMessageFromObjC: _handleMessageFromObjC
};

var messagingIframe;
  // 要發(fā)送給原生的消息列表
var sendMessageQueue = [];
  // 存儲注冊在bridge的JS方法
var messageHandlers = {};
// 要跳轉(zhuǎn)的URL
var CUSTOM_PROTOCOL_SCHEME = 'https';
var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
//JS方法回調(diào)
var responseCallbacks = {};
var uniqueId = 1;
var dispatchMessagesWithTimeoutSafety = true;
  // OC調(diào)用的JS方法需要用它來進行注冊
function registerHandler(handlerName, handler) {
  messageHandlers[handlerName] = handler;
}
//JS調(diào)用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;
}
// 要發(fā)送消息給原生了
function _doSend(message, responseCallback) {
  if (responseCallback) {
    var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
    responseCallbacks[callbackId] = responseCallback;
    message['callbackId'] = callbackId;
  }
  sendMessageQueue.push(message);
      //觸發(fā)webView 代理,解析JS 的message
  messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
  // 把消息轉(zhuǎn)成json字符串
function _fetchQueue() {
  var messageQueueString = JSON.stringify(sendMessageQueue);
  sendMessageQueue = [];
  return messageQueueString;
}

function _dispatchMessageFromObjC(messageJSON) {
  ....
}
// 原生會調(diào)用他,JS用它來達到消息分發(fā)
function _handleMessageFromObjC(messageJSON) {
      _dispatchMessageFromObjC(messageJSON);
}

messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);

setTimeout(_callWVJBCallbacks, 0);
function _callWVJBCallbacks() {
  var callbacks = window.WVJBCallbacks;
  delete window.WVJBCallbacks;
  for (var i=0; i<callbacks.length; i++) {
    callbacks[i](WebViewJavascriptBridge);
  }
}

我截選了一些關(guān)鍵代碼,首先整個WebViewJavascriptBridge_js是一個JS方法的執(zhí)行,首先創(chuàng)建了JS端的WebViewJavascriptBridge并賦值給了window,我們來看這個對象的構(gòu)成:

window.WebViewJavascriptBridge = {
  registerHandler: registerHandler,
  callHandler: callHandler,
  disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
  _fetchQueue: _fetchQueue,
  _handleMessageFromObjC: _handleMessageFromObjC
};
  • registerHandler:直接對應(yīng)下面的registerHandler(handlerName, handler) 方法,通過它我們把能被OC調(diào)用的JS方法進行注冊,看它的實現(xiàn)也是比較簡單的
function registerHandler(handlerName, handler) {
    messageHandlers[handlerName] = handler;
}

把JS的方法實現(xiàn)以方法名handleName保存在messageHandlers中。

  • callHandler: 對應(yīng)下面callHandler(handlerName, data, responseCallback)方法,通過它我們可以直接發(fā)起對OC方法的調(diào)用,具體調(diào)用邏輯我們在下面進行分析。
  • disableJavscriptAlertBoxSafetyTimeout:回調(diào)是否超時開關(guān),默認為false
  • _fetchQueue: 把javascript環(huán)境的方法序列化成JSON字符串,并返回給OC端
  • _handleMessageFromObjC:處理OC發(fā)給javascript環(huán)境的方法,_dispatchMessageFromObjC(messageJSON)這個方法的參數(shù)就是OC調(diào)用JS的message信息,這個方法對messageJSON進行解析處理,進而調(diào)用相應(yīng)的JS方法。
messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);

這里的src就是https://wvjb_queue_message,這段代碼的意思是把javascript要發(fā)送給OC的消息立即發(fā)送出去。

setTimeout(_callWVJBCallbacks, 0);
function _callWVJBCallbacks() {
  var callbacks = window.WVJBCallbacks;
  delete window.WVJBCallbacks;
  for (var i=0; i<callbacks.length; i++) {
    callbacks[i](WebViewJavascriptBridge);
  }
}

WebViewJavascriptBridge_js的最后是上面的代碼,它會調(diào)用ExampleApp.html中的callBack方法,也就是它

setupWebViewJavascriptBridge(function(bridge) {
 ....
})

繼而完成對這個JS環(huán)境的初始化與ExampleApp.html的加載。

(3) JS調(diào)用OC方法流程

  • 點擊JS按鈕觸發(fā)下面的方法
bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
    log('JS got response', response)
})

傳遞方法名testObjcCallback,消息參數(shù){'foo': 'bar'},以及OC回調(diào)JS的方法function(response) {log('JS got response', response)}),

  • 調(diào)用WebViewJavascriptBridge_js
function callHandler(handlerName, data, responseCallback) {
  if (arguments.length == 2 && typeof data == 'function') {
    responseCallback = data;
    data = null;
  }
  _doSend({ handlerName:handlerName, data:data }, responseCallback);
}

....

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;
}

可以看到核心方法是_doSend(),入?yún)essage是調(diào)用OC的方法名與參數(shù),responseCallback是OC回調(diào)JS的方法,接下來將這個回調(diào)方法保存在responseCallbacks中,key值是callbackId,消息對象message也添加一個callbackId,最后設(shè)置messagingIframe的src屬性,從而被ebView的代理方法- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler攔截。

  • 在上面的代理方法中,攔截到的URL為__wvjb_queue_message__,所以調(diào)用方法:
- (void)WKFlushMessageQueue {
    [_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
        if (error != nil) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
        }
        [_base flushMessageQueue:result];
    }];
}

....

- (NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}

WebView觸發(fā)JS的WebViewJavascriptBridge._fetchQueue(),

function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}

這個方法里面會將sendMessageQueue換成json字符串,然后返回給OC環(huán)境,觸發(fā)[_base flushMessageQueue:result];

- (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;
    }
    // JS傳遞過來的json字符串,我們進行反序列化 得到message數(shù)組
    NSLog(@"messageQueueString===%@",messageQueueString);
    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) { // 有回調(diào)
                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;
            }
            //向下傳遞參數(shù),并且觸發(fā)block回調(diào)()
            handler(message[@"data"], responseCallback);
        }
    }
}

這個方法就是OC端處理JS的核心方法了,將messageQueueString反序列化,得到消息數(shù)組

(
       {
       callbackId = "cb_1_1639553291614";
       data =         {
           foo = bar;
       };
       handlerName = testObjcCallback;
   }
)

callbackId,表明有消息回調(diào),生成responseCallbackBlock,這個Block里面將接收的參數(shù)與callbackId打包一并發(fā)送給JS環(huán)境,并調(diào)用JS環(huán)境的WebViewJavascriptBridge._handleMessageFromObjC(messageJSON);方法將messageJSON進行解析。這里只是Block的實現(xiàn),并沒調(diào)用這個Block,調(diào)用在下面

WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];

if (!handler) {
    NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
    continue;
}
//向下傳遞參數(shù),并且觸發(fā)block回調(diào)()
handler(message[@"data"], responseCallback);

根據(jù)handlerName找到在messageHandlers保存的方法實現(xiàn),handler(message[@"data"], responseCallback);進行真正的調(diào)用,在OC的注冊方法中,調(diào)用responseCallback(@"Response from testObjcCallback"); 向JS環(huán)境發(fā)送回調(diào)并傳遞參數(shù)。

  • JS環(huán)境通過這個_handleMessageFromObjC(messageJSON)方法得到messageJSON,并對其解析。

    function _dispatchMessageFromObjC(messageJSON) {
      if (dispatchMessagesWithTimeoutSafety) {
        setTimeout(_doDispatchMessageFromObjC);
      } else {
         _doDispatchMessageFromObjC();
      }
    
      function _doDispatchMessageFromObjC() {
      //轉(zhuǎn)換為對象
        var message = JSON.parse(messageJSON);
        var messageHandler;
        var responseCallback;
        // 這個responseId就是JS調(diào)用OC方法保存的callbackId
        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 });
            };
          }
    
          var handler = messageHandlers[message.handlerName];
          if (!handler) {
            console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
          } else {
            handler(message.data, responseCallback);
          }
        }
      }
    }
    

    先將字符串轉(zhuǎn)化為JSON對象,根據(jù)responseId(這個responseId就是JS調(diào)用OC方法保存的callbackId),找到對應(yīng)的方法實現(xiàn),進行調(diào)用

    function(response) {
      log('JS got response', response)
    }
    

    到此就完成了JS調(diào)用OC,并且OC回調(diào)JS并傳遞參數(shù)的全部過程。

2. OC調(diào)用JS方法

跟上面類似再來看下OC主動調(diào)用JS方法的實現(xiàn)

[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
....
 - (void)callHandler:(NSString *)handlerName data:(id)data {
    [self callHandler:handlerName data:data responseCallback:nil];
}

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
    [_base sendData:data responseCallback:responseCallback handlerName: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];
}
....
  
- (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];
    NSLog(@"javascriptCommand==%@",javascriptCommand);
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

可以看到,跟JS調(diào)用OC方法的原理類似,將OC調(diào)用JS的方法名與參數(shù)封裝進message對象,如果有回調(diào)函數(shù),將回調(diào)函數(shù)通過responseCallbacks保存,并生成callbackId,將整個message打包發(fā)送給JS環(huán)境的WebViewJavascriptBridge._handleMessageFromObjC(messageJSON); 進行解析,解析流程上面介紹過了,這里不再贅述。

四. 總結(jié)

通過上面流程分析,整個WebViewJavascriptBridge 內(nèi)部的實現(xiàn)原理就比較清晰了。

  • JS將方法注冊到JS環(huán)境的bridge,OC調(diào)用JS的核心方法就是[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];,JS環(huán)境收到消息后,通過方法WebViewJavascriptBridge._handleMessageFromObjC();將消息進行解析,調(diào)用注冊在bridge的方法。
  • OC將方法注冊到OC環(huán)境的bridge,JS調(diào)用OC的核心邏輯是,設(shè)置空白網(wǎng)頁的src屬性,從而被webView的代理方法- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler,OC通過核心方法- (void)flushMessageQueue:(NSString *)messageQueueString將傳遞數(shù)據(jù)進行解析。
  • 兩邊都是通過方法名找到對應(yīng)的方法實現(xiàn),然后通過ID來查找回調(diào)函數(shù)。

多閱讀源碼是好的一方面可以提升自己源碼閱讀水平,另一方面可以學習作者的一些好的設(shè)計思路。

參考:

https://github.com/marcuswestin/WebViewJavascriptBridge

https://github.com/lzyzsd/JsBridge

https://juejin.cn/post/6844903472718938126#heading-9

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容