In the recent two years of the project, 80% of the pages in the APP have been changed to H5, aiming at rapid development and convenient hot update. In addition to ensuring high code reuse and reducing coupling, the front end should actively communicate with students in charge of Native development, define protocols and leave documents for later maintenance.

At the heart of hybrid development is data interaction:

  • Native end: the use of the current market matureWebViewJavaScriptBridgeOpen source libraries. in AndroidThe end stepped in some holes,iOSThere were no major problems at the end.

The project is completed, take some time to learn the source code of WebViewJavaScriptBridge, and make a summary of the Hybrid development.

I. Interaction between JavaScript and Native (take iOS as an example)

1. JS calls Native methods:

  • Native intercepts URL redirect
Window.location. href = 'Native custom protocol ';Copy the code
  • useWebKitApple officially recommends using this method
window.webkit.messagehandlers.<name>.postMessage('xxx');
Copy the code

2. Native calls JS methods

The front-end exposes JS functions to the global Window object, and Native injects JS code execution into the webView.

If the above methods are used alone, they are troublesome and the code is difficult to organize. Native intercepting URL jump or using WebKit is more often used in the front-end one-way call to Native methods. Return and callback are not supported, and only send operation can be performed. You can’t do get.

3. Use WebViewJavaScriptBridge open source library for iOS and Android

  • iOS WebViewJavascriptBridge
  • Android JsBridge

### WebViewJavaScriptBridge

WebViewJavaScriptBridge solves the communication problem between JS and Native very well, and enables us to better organize the code. Its principle is further encapsulated according to the above two methods.

JS and Native need to call each other, so they need to do two things respectively: 1. Register methods for the other party to call; 2

Apis exposed by iOS (WKWebView) :

- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
- (void)removeHandler:(NSString*)handlerName;

- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

- (void)reset;
- (void)setWebViewDelegate:(id)webViewDelegate;
- (void)disableJavscriptAlertBoxSafetyTimeout;
Copy the code

Apis exposed by JavaScript:

window.WebViewJavascriptBridge = {
  registerHandler: registerHandler,
  callHandler: callHandler,
  disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
  _fetchQueue: _fetchQueue,
  _handleMessageFromObjC: _handleMessageFromObjC
};
Copy the code

We usually use their respective registerHandler and callHandler methods.

WebViewJavaScriptBridge directory structure

1, WebViewJavascriptBridgeBase

The core class for Bridge initialization and message processing holds three important properties: responseCallbacks: a callback module used to hold calls between objective-C and javascript environments. The _uniqueId timestamp is used to determine the callback for each call.

MessageHandlers: Used to store methods registered in objective-C, with key as the method name and value as the corresponding callback block

StartupMessageQueue: Saves messages that need to be sent to the JavaScirpt environment during class instantiation.

2, WebViewJavascriptBridge

Bridge entry class to determine whether the current WebView type is UIWebView or WKWebView and perform the corresponding logic.

3, WKWebViewJavascriptBridge

WKWebView against doing a layer of packaging, mainly used to perform the JS code, and the methods of how to realize WKWebView agent, and by intercepting the URL to inform WebViewJavascriptBridgeBase do the corresponding operation. Study the source code, is given priority to with WKWebViewJavascriptBridge, ignoring the UIWebView.

4, WebViewJavascriptBridge_JS

The code is injected into the WebView for the register and Call operations on the JavaScript side.

The addition is mainly aimed at

  • iOSInitialize theWebViewJavascriptBridge
  • JavaScript Initialize theBridge
  • JavaScriptTake the initiative to calliOSmethods
  • iOSTake the initiative to callJavaScript methods

These four processes are explained in depth

4. IOS initializationWebViewJavascriptBridge

In ExampleWKWebViewController. M file, initialize the brige

self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webview];
Copy the code

Come to WebViewJavascriptBridge source directory, open the WebViewJavascriptBridge. M file, find bridgeForWebView method, the code is as follows:

Bridge + (instanceType)bridgeForWebView:(id)webView {return [self bridge:webView]; } + (instancetype)bridge:(id)webView {// check whether WKWebView is supported by supportsWKWebView // if so, Perform WKWebViewJavascriptBridge class methods bridgeForWebView # if defined supportsWKWebView if ([webView isKindOfClass: [WKWebView class]]) { return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView]; } #endif if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) { WebViewJavascriptBridge* bridge = [[self alloc] init];  [bridge _platformSpecificSetup:webView]; return bridge; } [NSException raise:@"BadWebViewType" format:@"Unknown web view type."]; return nil; }Copy the code

Open the WKWebViewJavascriptBridge. M file, found:

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

After the bridge is instantiated, the _setupInstance and reset methods are executed:

- (void) _setupInstance:(WKWebView*)webView {// save webView _webView = webView; NavigationDelegate is of type WKNavigationDelegate, Defines many WKWebView proxy method in the process of running behind / / to use its intercept URL function _webView navigationDelegate = self; / / instantiate WebViewJavascriptBridgeBase _base = [[WebViewJavascriptBridgeBase alloc] init]; / / set the delegate of WebViewJavascriptBridgeBase, convenient call _evaluateJavascript _base. Delegate = self; }Copy the code
- (void) reset {/ / call WebViewJavascriptBridgeBase reset method [_base reset]; }Copy the code
(void)reset {// Reset three important member variables: startupMessageQueue, responseCallbacks, and _uniqueId A callback module used to hold calls between objective-C and javascript environments. The _uniqueId timestamp is used to determine the callback for each call. // startupMessageQueue: Saves the messages that need to be sent to the JavaScirpt environment during class instantiation. self.startupMessageQueue = [NSMutableArray array]; // Array self.responseCallbacks = [NSMutableDictionary dictionary]; // dictionary _uniqueId = 0; }Copy the code

Because the webview navigationDelegate attribute points to the WKWebViewJavascriptBridge instance, Then the webview shall have the right to perform some the implemented method of WKWebViewJavascriptBridge instance, such as:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction DecisionHandler :(void (^)(WKNavigationActionPolicy))decisionHandler {if (webView! = _webView) { return; } NSURL *url = navigationAction.request.URL; __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate; / / judgment whether the front and Native negotiated agreement format if ([_base isWebViewJavascriptBridgeURL: url]) {/ / whether the webView the loaded the url of the if ([_base IsBridgeLoadedURL :url]) {// Inject javascript string // in webViewjavascriptBridge_js. m to access WebViewJavascriptBridge through the global Window object The [_base injectJavascriptFile]; Else if ([_base isQueueMessageURL:url]) {// Refresh MessageQueue queue [self WKFlushMessageQueue]; } else {// Unknown message [_base logUnkownMessage:url]; } / / that cancels the execution of the normal HTTP request process, Native my remaining logical decisionHandler processing (WKNavigationActionPolicyCancel); return; } // If the URL format is not negotiated between the front-end and Native, It is a normal HTTP request if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) { [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler]; } else { decisionHandler(WKNavigationActionPolicyAllow); }}Copy the code

As can be seen from the above code, the way Native obtains the message sent by JS is to intercept the URL request.

5. JavaScript initializes the Bridge

The front-end uses the WebViewJavascriptBridge under the global window object, which is derived from the JS string in the Native injected webview javascriptbridge_js. m.

The front end needs to use the WebViewJavascriptBridge object to do some initialization work. Every time the method of call Native end needs to actively change the SRC attribute of iframe to achieve the effect of using url request. Native can then intercept the request. The specific code is as follows for your reference.

const u = window.navigator.userAgent; const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; const isiOS = !! u.match(/\(i[^;] +; ( U;) ? CPU.+Mac OS X/); if (isAndroid) { initAndroidBridge(bridge => { bridge.init((message, responseCallback) => { responseCallback(); }); // If you need to call the native method immediately after the page initialization is complete // then you need to subscribe to the corresponding page "page initialization completion message" // then trigger the active // such as Vue EventBus. }); } function initAndroidBridge(callback) { if (window.WebViewJavascriptBridge) { callback(window.WebViewJavascriptBridge);  } else { document.addEventListener('WebViewJavascriptBridgeReady', () => { callback(window.WebViewJavascriptBridge); }, false); } } function initIOSBridge(callback) { if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } window.WVJBCallbacks = [callback]; const WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'https://__bridge_loaded__'; document.documentElement.appendChild(WVJBIframe); setTimeout(() => { document.documentElement.removeChild(WVJBIframe); }, 0); } const call = (function () { if (isiOS) { return function (name, data, callback) { initIOSBridge(bridge => bridge.callHandler(name, data, callback)); } } return function (name, data, callback) { window.WebViewJavascriptBridge.callHandler(name, data, callback); }}) (); const register = (function () { if (isiOS) { return function (name, callback) { initIOSBridge(bridge => bridge.registerHandler(name, callback)); } } return function (name, callback) { window.WebViewJavascriptBridge.registerHandler(name, callback); }}) (); export default { call, register }Copy the code

JavaScript calls iOS

JS calls Native actively. First, Native should register the corresponding method. Therefore, JS uses the register method to call the registered method of Native.

   [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];
Copy the code

RegisterHandler method in WebViewJavascriptBridge. M file

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    // Assign to the messageHandlers dictionary
    // handlerName as the key name
    // Handler is copied as a key
    _base.messageHandlers[handlerName] = [handler copy];
}
Copy the code

With registration complete, the front end can be called:

window.WebViewJavascriptBridge.callHandler(handlerName, data, callback)
Copy the code

As mentioned earlier, Native executes the [_base injectJavascriptFile] method to inject the WebViewJavascriptBridge_JS code into the WebView, So there is a WebViewJavascriptBridge on the window global object, that is:

window.WebViewJavascriptBridge = {
  callHandler,
  registerHandler,
  / /... Other methods
};
Copy the code

So look at the WebViewjavascriptBridge_js. m file and find the callHandler method:

function callHandler(handlerName, data, responseCallback) {
  // Parameter overload, data can be passed or not
  if (arguments.length == 2 && typeof data == 'function') {
    responseCallback = data;
    data = null;
  }
  // Execute the _doSend method
  _doSend({ handlerName:handlerName, data:data }, responseCallback);
}
Copy the code

Take a look at what the _doSend method does:

function _doSend(message, responseCallback) {
  // responseCallback is also optional, with scenarios like this:
  // After the front-end call Native method, there is no need for Native response, i.e. one-way communication
  if (responseCallback) {
    // In a front-end call, the callbackId consists of cb prefix, uniqueId, and timestamp
    var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
    The responseCallbacks object holds the responseCallback corresponding to the callbackId
    responseCallbacks[callbackId] = responseCallback;
    // Add the callbackId attribute to message with the value calbackId
    message['callbackId'] = callbackId;
  }
  // Put this message in the sendMessageQueue queue
  sendMessageQueue.push(message);
  // Native receives the core of the front-end message: if you change the SRC attribute of the iframe, Native intercepts it
  // Native determines whether the scheme is agreed with the front-end and performs specific processing
  messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ': / /' + QUEUE_HAS_MESSAGE;
}
Copy the code

Native registers the method and the front end is called. At this point, Native intercepts the URL request.

- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
    if(webView ! = _webView) {return; }
    
    NSURL *url = [request URL];
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
          // Native intercepts the call from the front-end
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        [listener ignore];
    } else if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector
      / /... omit
    } else {
      / /... omit}}Copy the code

The above code, which I have studied in the previous notes, is now focused on intercepting the message from the front-end call, namely:

// Execute the _evaluateJavascript method
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
Copy the code

First get “webViewjavascriptBridge._fetchQueue ();” This string, by calling [_base webViewJavascriptFetchQueyCommand] : method

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

This is then handed to _evaluateJavascript, which executes the JS string code

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {
    return [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
}
Copy the code

Look at the javascript string code in the webviewjavascriptBridge_js. m file:

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

SendMessageQueue is an array string with elements of the following format:

var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
[
  {
    handlerName,
    data,
    callbackId
  }
]
// Remember that the responseCallbacks object holds the responseCallback corresponding to the callbackId
responseCallbacks[callbackId] = responseCallback;
Copy the code

[_base flushMessageQueue] : fetchQueue messageQueueString [_base flushMessageQueue]

- (void)flushMessageQueue:(NSString *)messageQueueString{
  // messageQueueString must be a qualified parsed array of strings
    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;
    }
    // Parse the front-end string object into an array
    id messages = [self _deserializeMessageJSON:messageQueueString];

    for (WVJBMessage* message in messages) {
      // Each message of an array element must be a qualified dictionary structure
        if(! [message isKindOfClass:[WVJBMessageclass]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }
        [self _log:@"RCVD" json:message];
        
        NSString* responseId = message[@"responseId"];
        // Call Native responseId ()
        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            // responseCallback is a block
            WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                // responseCallback
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            // Remember? Native registration method executes:
            // _base.messageHandlers[handlerName] = [handler copy];
            // Now fetch the corresponding handler according to the handlerName specified by the front-end convention
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]].if(! handler) {// Call Native unregistered methods
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            // Pass in the corresponding argument and execute handler
            // That is, Native receives the message from the front-end call and needs to execute its logic
            handler(message[@"data"], responseCallback); }}}Copy the code

The handler of the above code is the Native registration method:

self.bridge registerHandler:@"login" handler:^(id data, WVJBResponseCallback responseCallback) {
  NSString *response = @"this is a responsive message from native";
  // The responseCallback block is defined when the responseId does not exist
  // Pass in the response argument that needs to be returned to the front end
  responseCallback(response);
}];
Copy the code

So what does the responseCallback do? Copy the code again for easy viewing:

responseCallback = ^(id responseData) {
    if (responseData == nil) {
      // return null if responseData does not exist
        responseData = [NSNull null];
    }
    
    // reassemble the MSG, execute self(base) _queueMessage
    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
    [self _queueMessage:msg];
};
Copy the code
- (void)_queueMessage:(WVJBMessage*)message {
  // The WKWebview initialization is complete, and the startupMessageQueue is set to nil when injectJavascriptFile is executed
  // self.startupMessageQueue = nil;
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else{[self_dispatchMessage:message]; }}Copy the code

Let’s look at the _dispatchMessage logic and remember the MSG structure:

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
Copy the code
- (void)_dispatchMessage:(WVJBMessage*)message {
    // Convert the field to a string
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];

    // omit a series of escape codes..
   
    // Concatenates strings using the stringWithFormat method
    // The last js code executed is equivalent to: WebViewJavascriptBridge._handleMessageFromObjC(message)
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self_evaluateJavascript:javascriptCommand]; }); }}Copy the code

Find the _handleMessageFromObjC method in the webViewjavascriptBridge_js. m file:

function _handleMessageFromObjC(messageJSON) {
  // _handleMessageFromObjC does a relay, actually executing the _dispatchMessageFromObjC method
    _dispatchMessageFromObjC(messageJSON);
}
Copy the code

Here, it indicates that Native has officially handed over control to JS, execute _dispatchMessageFromObjC method:

function _dispatchMessageFromObjC(messageJSON) {
		if (dispatchMessagesWithTimeoutSafety) {
			setTimeout(_doDispatchMessageFromObjC);
		} else {
			 _doDispatchMessageFromObjC();
		}
		
		function _doDispatchMessageFromObjC() {
      // Convert to familiar JSON
			var message = JSON.parse(messageJSON);
			var messageHandler;
			var responseCallback;

      // The responseId exists in messageJSON
      // copy the MSG structure:
      // WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
			if (message.responseId) {
        // Remember? When the front end calls the Native method, it executes the _doSend method
        // Use the responseCallbacks object to hold the responseCallback corresponding to the callbackId
        // responseCallbacks[callbackId] = responseCallback;
        // Callback callback is now executed according to responId (i.e. the current callbackId)
        // That is, the Native logic runs out and needs to start its own callback
				responseCallback = responseCallbacks[message.responseId];
				if(! responseCallback) {return;
        }
        // This is the responseCallback callback that we wrote when we called, now passed to message.responseData to execute
        responseCallback(message.responseData);
        // When the call is complete, delete the callback reference
				delete responseCallbacks[message.responseId];
			} else {
				/ /... Omit currently unused code...}}}Copy the code

7. IOS actively calls JavaScript methods

Native calls JS actively, and JS must register the corresponding method first. Therefore, this section notes that JS uses register to register the method, and then Native uses call to call the method registered by JS.

window.WebViewJavascriptBridge.registerHandler(handlerName, callback)
Copy the code

The registerHandler method is in the webViewjavascriptBridge_js.m file:

function registerHandler(handlerName, handler) {
  // Same as Native registration method
  // Assign to the messageHandlers object
  // handlerName as the key name
  // Handler is copied as a key
  messageHandlers[handlerName] = handler;
}
Copy the code

Registration complete, Native can be called:

[self.bridge callHandler:@"handlerName" data:@"event from native" responseCallback:^(id responseData) {
    NSLog(@"message from JavaScript: %@", responseData);
}];
Copy the code

Found in WKWebViewJavascriptBridge. M callHandler method:

- (void)callHandler:(NSString *)handlerName {
    [self callHandler:handlerName data:nil responseCallback:nil];
}

- (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];
}
Copy the code

As an example, we pass in all three arguments: handlerName, data, and responseCallback, which executes the third method above:

 [_base sendData:data responseCallback:responseCallback handlerName:handlerName];
Copy the code

In WebViewJavascriptBridgeBase. M file to find the corresponding methods:

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    // Define a message dictionary
    NSMutableDictionary* message = [NSMutableDictionary dictionary];
    
    // Assign data to the message dictionary
    if (data) {
        message[@"data"] = data;
    }
    
    if (responseCallback) {
        // This is the same as the JS call Native method
        // Concatenate a unique callbackId
        // put responseCallback in the responseCallbacks dictionary
        NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
        self.responseCallbacks[callbackId] = [responseCallback copy];
        message[@"callbackId"] = callbackId;
    }
    
    if (handlerName) {
        // Add handlerName to the message dictionary
        message[@"handlerName"] = handlerName;
    }

    // Pass the assembled message to the _queueMessage method and execute
    [self _queueMessage:message];
}
Copy the code

Remember the structure of the Message dictionary:

NSMutableDictionary* message = {
  @"data": data, // If there is data
  @"callbackId": @"objc_cb_ unique uniqueId".@"handlerName": handlerName
};
Copy the code

CallbackId is a copy of responseCallback in the responseCallbacks dictionary.

self.responseCallbacks[callbackId] = [responseCallback copy];
Copy the code

Let’s look at the _queueMessage method in detail,

- (void)_queueMessage:(WVJBMessage*)message {
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else{[self_dispatchMessage:message]; }}Copy the code

_dispatchMessage method:

- (void)_dispatchMessage:(WVJBMessage*)message {
    // Convert the message dictionary to an object string
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    / /... Omit a series of escape codes
    
    // Get an executable javascriptCommand just like javascript calls Native
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self_evaluateJavascript:javascriptCommand]; }); }}Copy the code

WebViewJavascriptBridge._handleMessageFromObjC method: Remember the message structure?

NSMutableDictionary* message = {
  @"data": data, // If there is data
  @"callbackId": @"objc_cb_ unique uniqueId".@"handlerName": handlerName
};
Copy the code

Convert message to a string and pass it to _dispatchMessageFromObjC for execution

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 { // When we see the message structure, we still go to the else branch
      if (message.callbackId) {
        var callbackResponseId = message.callbackId;
        responseCallback = function(responseData) {
          _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
        };
      }
      
      // messageHandlers[handlerName] = handler;
      // Use handlerName
      var handler = messageHandlers[message.handlerName];
      if(! handler) {console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
      } else {
        / / handler executionhandler(message.data, responseCallback); }}}}Copy the code

A handler is a callback to a method registered with JS:

window.WebViewJavascriptBridge.registerHandler(handlerName, function handler (data, responseCallback) {
  / /... After completing the JS logic, you need to call the second or third argument to the Native call JS method, which is called as a callback.
  var dataFromJs = {
    name: 'zhaoyiming'.age: 18
  };
  responseCallback(dataFromJs);

  // - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
  // [_base sendData:data responseCallback:responseCallback handlerName:handlerName];
  // }
})
Copy the code

But there is a problem. Native can execute JS methods directly, JS can’t execute Native methods directly. Else responseCallback = responseCallback;

responseCallback = function(responseData) {
  // Execute the _doSend method in our custom responseCallback and see what is done to get JS to "indirectly" call Native methods
  _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
Copy the code

_doSend method:

According to the above implementation, the message structure here is:const message = {
  handlerName,
  responseId: callbackResponseId, // This time the responseId is available
  responseData
};
function _doSend(message, responseCallback) {
  // The responseCallback is undefined, so the responseCallback does not go to the logic in the if block
  if (responseCallback) {
    var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
    responseCallbacks[callbackId] = responseCallback;
    message['callbackId'] = callbackId;
  }

  // Remember sendMessageQueue? The last note was used when learning how to call Native methods with JS
  // It is an array that can hold many messages
  sendMessageQueue.push(message);

  // *** *** **
  // The front-end modifies the SRC attribute value of iframe to actively trigger Native interception (the process of JS calling Native methods)
  // Native can then take the data in sendMessageQueue and parse it
  messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ': / /' + QUEUE_HAS_MESSAGE;
}
Copy the code

At this point, the logic goes back to Native’s method of intercepting the URL request. Actually eventually webview implement stringByEvaluatingJavaScriptFromString method and incoming sendMessageQueue string objects.

Then came to flushMessageQueue method (WebViewJavascriptBridgeBase), responseId have value this time:

- (void)flushMessageQueue:(NSString *)messageQueueString{
    / /... Omit the judgment

    id messages = [self _deserializeMessageJSON:messageQueueString];
    for (WVJBMessage* message in messages) {
        / /... Omit the judgment
        
        NSString* responseId = message[@"responseId"];
        if (responseId) {
            // When the sendData method is executed, the callback from the Native active JS method is placed in the responseCallbacks
            // self.responseCallbacks[callbackId] = [responseCallback copy];
            // Now use it
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            // responseCallback executed, JS successfully called Native method indirectly
            responseCallback(message[@"responseData"]);
            // After the call succeeds, delete the redundant references
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            / /... So this time we go to the if branch above, and the responseId has a value}}}Copy the code

At this point, the whole logic of Native actively calling JS methods is finished.

Thank you for reading!!