WebViewJavascriptBridge建立bridge、Web调用OC、OC调用Web(强烈呼吁摒弃百度搜索,推荐google、必应)

1.提出问题

  • 诉求
    在一个Web界面中,需要OC代码直接调用Web中的js函数,同时也需要Web中的JS代码直接调用OC中方法。
    iOS提供可实现原理:通过webView中的方法拦截Web界面分析request值的内容后,或根据约定的字段调用移动端对应的代码执行,或编写JavaScript代码操作Web界面。
UIWebView
// 拦截Web的request
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    NSURL *url = [request URL];
    ``` 其他操作```
   }
// 操作Web界面方法
[self.webView stringByEvaluatingJavaScriptFromString:javascriptSting];
-----------------------------------------------------------------------------------------------------------
WKWebView
// 拦截Web的request
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
  }
// 操作Web界面方法
[self.webView evaluateJavaScript:javascriptCommand completionHandler:nil];

2.使用WebViewJavascriptBridge解决

WebViewJavascriptBridge:An iOS/OSX bridge for sending messages between Obj-C and JavaScript in WKWebViews, UIWebViews & WebViews.

WebViewJavascriptBridge就是通过上面👆的原理封装而成,为我们建造了一座桥(bridge)。桥的本质:其实就类似于在OC端和Web端各创建一个名字叫bridge的通讯兵(OC: WKWebViewJavascriptBridge/WebViewJavascriptBridge,Web: WebViewJavascriptBridge_JS),这两个通讯兵有一套相同的处理信息机制,然后根据 【对方信息处理后的结果】找到自己端对应的事件后,执行该事件,那么这个桥就是通讯兵互相传递消息的行为。

下图为WebViewJavascriptBridge的代码结构:
- WebViewJavascriptBridge_JS (负责存储Web端的事件和回调,以及向OC端接受和发送json串指令)
- WebViewJavascriptBridgeBase (负责存储OC端的事件和回调,以及向Web端接受和发送json串指令)
- WKWebViewJavascriptBridge、WebViewJavascriptBridge (是WebViewJavascriptBridgeBase的接口层以供针对不同容器使用(UIWebView和WKWebView),同时也负责拦截Web端request,供WebViewJavascriptBridgeBase使用)

WebViewJavascriptBridge代码结构.png

3. Web端Bridge的实现过程

OC端创建bridge很简单只需要WKWebViewJavascriptBridge/WebViewJavascriptBridge创建一个实例对象,调用相关方法即可,具体可见下面代码。而Web端创建bridge需要经历一些事情,以下内容便是Web端bridge创建过程。

  • 准备建立bridge :
    Web端,将下面👇function代码粘贴在Web端的JS代码中(添加这段代码是:由前端人员开发H5界面时在JS代码中添加的),以备调用去建立bridge。

    OC端,初始化一个WebView和WebViewJavascriptBridge对象(WKWebView和WKWebViewJavascriptBridge对象),并将WebView的代理交给WebViewJavascriptBridge对象实现。

*JS创建bridge
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)
}
*OC创建bridge
- (void)viewWillAppear:(BOOL)animated {
    if (_bridge) { return; }
    UIWebView* webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    _bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [_bridge setWebViewDelegate:self];
    [self.view addSubview:webView];

    // 注册OC端的事件,以备JS端调用
    [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];
   
}

*Web端建立bridge是在OC加载Web界面时,Web中JS代码调用function setupWebViewJavascriptBridge(callback) {...}建立bridge,但是真正创建Web端的bridge是在WebViewJavascriptBridge_JS.m文件中的JS代码中。那么怎么才能让WebViewJavascriptBridge_JS.m文件中的JS代码运行呢?接着往下看

setupWebViewJavascriptBridge(function(bridge) {
    bridge.registerHandler('JS Echo', function(data, responseCallback) {
        console.log("JS Echo called with:", data)
        responseCallback(data)
    })
    bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
        console.log("JS received response:", responseData)
    })
})

调用setupWebViewJavascriptBridge方法之后发生了什么呢?注意下面代码注释。

// OC端加载Web时,Web端调用setupWebViewJavascriptBridge方法
setupWebViewJavascriptBridge(
        // function(bridge){...} 就是setupWebViewJavascriptBridge方法参数callback。
        function(bridge) {
           // Web端通过桥bridge注册OC端调用JS端的相关事件,以备OC端调用,方法registerHandler(handleName , handle )声明实现在文件WebViewJavascriptBridge_JS.m中。
           bridge.registerHandler(
                           // handleName  
                           'JS Echo', 
                           // handle
                           function(data, responseCallback) {
                                console.log("JS Echo called with:", data)
                                responseCallback(data) 
                             }
                   )
         }
)

// 调用方法,将参数callback传进来
function setupWebViewJavascriptBridge(callback) {
    //判断window中是否有WebViewJavascriptBridge属性,属性WebViewJavascriptBridge其实就是桥(bridge),这个属性的声明和初始化在文件WebViewJavascriptBridge_JS.m中。
    //如果bridge存在,则直接执行callback,在callback中注册OC端调用JS端的相关事件。如果不存在则略过。
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    //判断WVJBCallbacks是否存在,该属性在下行有声明和实现,主要负责临时存储callback,等待bridge建立成功后,再将callback拿出来运行,注册OC端调用JS端的相关事件。
    // 如果WVJBCallbacks存在,则将callback存起来,如果不存在则继续执行,初始化WVJBCallbacks属性并将callback存起来。
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    // 初始化WVJBCallbacks属性并将callback存起来。
    window.WVJBCallbacks = [callback];
    // 初始化一个WVJBIframe元素,设置其隐藏不显示,设定其链接url为 'https://__bridge_loaded__',然后添加到Web后执行了src的请求,随后OC的WebView的代理方法会拦截到request,对url进行比对,如果url为https://__bridge_loaded__则会将WebViewJavascriptBridge_JS.m中的JS代码注入Web界面并执行,创建出bridge(具体实现过程在OC端实现,稍后再说)
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
// 当代码执行至此,bridge建立,callback调用已经完成,这些操作均在文件WebViewJavascriptBridge_JS.m中的JS代码中执行。那下面一句操作,便是移除WVJBIframe元素,因为它的使命已经完成(使命:发起一个请求,能使OC端拦截url)。
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
  • Web端JS代码执行到:document.documentElement.appendChild(WVJBIframe);后,OC的WebView的代理方法会拦截到request,对url进行比对,如果url为 https://__bridge_loaded__ 则会将WebViewJavascriptBridge_JS.m中的JS代码注入并执行。
// WebView的代理方法,WebViewJavascriptBridge.m中的代码,代码中的_base: WebViewJavascriptBridgeBased实例对象
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    // 判断截获的URL是否是桥接所用到的url(桥接共使用了两个url,1. https://__bridge_loaded__仅用于建立桥,2.https://__wvjb_queue_message__仅用于Web呼唤OC端处理事务使用)
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        
        if ([_base isBridgeLoadedURL:url]) { // 判断url是否为https://__bridge_loaded__
            // 建立桥,OC端将WebViewJavascriptBridge_JS.m中的JS代码注入Web界面并执行
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) { 判断url是否为https://__wvjb_queue_message__,只有在桥建立完成后才会调用这个url
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}
  • 没错,就在这里我们调用到了WebViewJavascriptBridge_JS.m中的JS代码。代码执行到[_base injectJavascriptFile];时,说明Web端通过https://__bridge_loaded__呼唤到了OC端,并通过拦截requst,告诉OC端要建立桥。具体见 injectJavascriptFile 函数代码
- (void)injectJavascriptFile {
    // 获取WebViewJavascriptBridge_JS.m文件中的JS代码
    NSString *js = WebViewJavascriptBridge_js();
   // 以代理的方式,回调回去,让WebView来执行 [self.webView stringByEvaluatingJavaScriptFromString:javascriptSting];
    [self _evaluateJavascript:js];
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}
  • 那么接下来看一下JS代码中做了什么
// 初始化了bridge,{···}中是bridge相关的方法
window.WebViewJavascriptBridge = {
        registerHandler: registerHandler,
        callHandler: callHandler,
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    };
    // 初始化一个messagingIframe,加载url:https://__wvjb_queue_message__,OC端拦截到url,又做了一些列操作,稍后再说
    messagingIframe = document.createElement('iframe');
    messagingIframe.style.display = 'none';
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    document.documentElement.appendChild(messagingIframe);

    registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
    //待上面操作,全部执行完成之后,执行到setTimeout方法,这是js的方法,想了解的同学可以自查。之后调用_callWVJBCallbacks,在_callWVJBCallbacks中操作具体见注释
    setTimeout(_callWVJBCallbacks, 0);

    function _callWVJBCallbacks() {
        // 获取WVJBCallbacks属性,此属性在Web端添加的代码中有初始化,并将callback添加到WVJBCallbacks中
        var callbacks = window.WVJBCallbacks;
       // 清空WVJBCallbacks
        delete window.WVJBCallbacks;
       // 执行每一个callback,上面代码中的callback是一个function(bridge),调用此function需要传入参数bridge,所以下面代码中将WebViewJavascriptBridge传入
        for (var i=0; i<callbacks.length; i++) {
            callbacks[i](WebViewJavascriptBridge);
        }
    }
/*
//执行calback
 function(bridge) {
           // registerHandler(handleName , handle ) 在初始化WebViewJavascriptBridge时就声明,并在WebViewJavascriptBridge_JS.m文件中实现。
           bridge.registerHandler(
                           // handleName  
                           'JS Echo', 
                           // handle
                           function(data, responseCallback) {
                                console.log("JS Echo called with:", data)
                                responseCallback(data) 
                             }
                   )
         }

// registerHandler方法就是将handle保存起来
function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler; 
    }

*/
  • 执行完成以上以上以上···所有的代码后Web端的bridge已经创建完了,可以用了。程序接着往下运行,又回到了Web中添加的那个setupWebViewJavascriptBridge(callback) 方法。

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

    /*没错,执行完成以上以上以上···所有的代码后,该执行下面这句代码了,一切源于WVJBIframe
     发起了一个https://__bridge_loaded__请求,那么一切也以它结束,移除WVJBIframe。到此
     刻bridge已经创建,并且有可能将Web端需要注册和OC端注册的事件保存完成,等待调用*/
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

4.Web 调用 OC 实现过程

此时此刻,双方的bridge通讯兵已经创建完成。那么现在有一个任务出现了,Web端想让OC端买个苹果。当然这个任务不是随机想到的,其实事先预定好的,Web和OC约定好一个任务代号:buy_apple,任务实质是买苹果。
ok,首先OC端先给自己的bridge报备一下,有一个代号为buy_apple,内容买苹果的任务,即:注册任务。

// 注册任务
[_bridge registerHandler:@"buy_apple" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"buy_apple called: %@", data);
        ······
        responseCallback(@"买完了,你吃吗?");
    }];

// registerHandler方法将handler保存在数组messageHandlers中
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

现在OC端已经准备好了,随时等待Web端来调用。好的,时机已到!Web端把bridge喊道跟前,让他告诉OC端给老子买个苹果,快!于是乎。。。

// Web端bridge调用callHandler方法,handlerName是约定好的buy_apple,同时传入data(去哪里买),以及OC买完之后的回调。
bridge.callHandler('buy_apple', {'address': '北京新发地'}, function(response) {
                log('JS got response', response)
            })


以下方法均在WebViewJavascriptBridge_JS.m文件中JS代码中。

// bridge调用后传进来 handlerName, data, responseCallback(handler调用成功后的回调)
function callHandler(handlerName, data, responseCallback) {
        //因为js是弱语言,参数没有规定都必须传,所以在此判断参数是否对应。
        if (arguments.length == 2 && typeof data == 'function') {
            responseCallback = data;
            data = null;
        }
        // 将handlerName和data封装在一起变为一个message参数,再带上responseCallback参数,调用_soSend 方法
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }

//  _doSend函数
function _doSend(message, responseCallback) {
        // 判断responseCallback是否存在,如果存在则生成一个callbackId,以callbackId为key值,以responseCallback为value存到Web的全局变量responseCallbacks中。
        if (responseCallback) {
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
             // 同时将callbackId添加给message中做个元素
            message['callbackId'] = callbackId;
        }
        // 将message保存在全局变量sendMessageQueue中。
        sendMessageQueue.push(message);
       // 利用messagingIframe.src,发起一个请求(url:https://__wvjb_queue_message__)
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }        

ok ,执行至此,Web端的bridge捞起电话☎️“摩西摩西。。我是Web!”,OC端的bridge接到电话。

// 拦截Web端的request,分析url
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {//判断url后运行至此,接通了☎️
            /*OC端bridge问了一句话,什么话呢?
            就是:[_base webViewJavascriptFetchQueyCommand];
            跳到该方法发现是:@"WebViewJavascriptBridge._fetchQueue();"
            也是让Web端的bridge执行_fetchQueue();
            _fetchQueue()执行了什么见下面JS代码,Web端bridge回了话并值赋值给了messageQueueString
            */
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            // OC端bridge拿到了话,去调用flushMessageQueue方法做分析
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

*******JS代码********
function _fetchQueue() {
        // 将sendMessageQueue存放的messgae转成一个json字符串告诉OC端的bridge
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        return messageQueueString;
    }

Web端的bridge打电话告诉OC端的bridge一段信息,但是这段信息需要去分析一下,才能明白要做什么,他是怎么分析的呢?

// 分析返回的json字符串
- (void)flushMessageQueue:(NSString *)messageQueueString{
    // 判断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;
    }
     
    // 反序列化 message 得一个数组
    id messages = [self _deserializeMessageJSON:messageQueueString];
    
    // 循环这个数组
    for (WVJBMessage* message in messages) {

        // 判断成员是否为字典
        if (![message isKindOfClass:[WVJBMessage class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }

        /*responseId到底是干什么的?
         上文提到,Web端调用_doSend方法时,将responseCallback以callbackId
         存入responseCallbacks,那么买苹果这事儿执行完,要执行这个回调事件responseCallback
         告诉Web执行完了,给你个苹果。那这个回调事件呢,需要从responseCallbacks中根据
         callbackId找出来,然后执行。但是responseCallback,应该对应responseId才算工整是吧?
         同时也是区分对面的通讯兵给的是callback的message还是response的message。
         所以在解析callback的message时,会创建一个responseId,并将callbackId的值赋于它。*/ 
     
        // 获取responseId
        NSString* responseId = message[@"responseId"];
       // 根据responseId是否存在判断,对面的通讯兵给的是callback的message还是response的message。
        if (responseId) {
            // 如果是response的message,则根据responseId找到之前保存的responseCallback
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
           //将handler执行成功后返回的数据作为responseCallback回调的参数,执行responseCallback
            responseCallback(message[@"responseData"]);
           // 从self.responseCallbacks中删除已执行responseCallback
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
// 如果不存在responseId必定是callback的message
// 准备一个responseCallback,创造一个responseId用于在callback执行完成之后,告诉Web的bridge,并将他需要的东西给他。
            WVJBResponseCallback responseCallback = NULL;
// 取出callbackId,如果callbackId存在则赋值给responseId
            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
                };
            }
            
            //取出handlerName,然后根据handlerName从全局变量self.messageHandlers找到相对应的handler,然后执行。
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            // 判断是否存在handler
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            // handler有固定的格式,即参数1:data ,参数2:回调block
            handler(message[@"data"], responseCallback);
/*
   回忆注册任务时的handler,现在执行handler,message[@"data"] = {'address': '北京新发地'},
   所以NSLog打印出来是:buy_apple called: {'address': '北京新发地'},然后调用了回调responseCallback,
   并传入参数@“买完了,你吃吗?”

[_bridge registerHandler:@"buy_apple" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"buy_apple called: %@", data);
        ······
        responseCallback(@"买完了,你吃吗?");
    }];
*/
        }
    }
}

代码运行至此,OC端已经根据Web端的bridge说的信息,找到了handler后并执行了,执行之后OC端有执行了自己创造的responseCallback,那么我们具体看一下这个responseCallback

 responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    // 将回调的 信息同样做成一个messge。
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    // 将拼好的message处理成转义后的json字符串
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
    }

// 所有OC端bridge给Web的bridge发送的信息都要经过此方法,转成一个转义后的json字符串,就像Web的bridge发给OC的bridge也是一段json字符串。
- (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"];
    // OC的bridge将下面这个javascriptCommand交给了Web的bridge,Web的bridge拿到javascriptCommand后也会进行分析一下,获取到OC端传来的数据后,再执行操作。
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

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

Web的bridge执行了function _dispatchMessageFromObjC {····},但是最终运行了下面方法,查看下面方法后,其实与OC端的flushMessageQueue类似,这也印证了,OC和Web的bridge使用了相同的分析方法,分析对方传回来的message。如下代码会从message中取出responseId,根据responseId取出对应的responseCallback,然后将message中的data作为responseCallback的参数,运行responseCallback。最终执行log('JS got response', response),打印出信息。

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 });
                    };
                }
                
                var handler = messageHandlers[message.handlerName];

                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    handler(message.data, responseCallback);
                }
            }
        }
    }

5. OC 调用 Web 实现过程

通过上面详细讲述的Web调用 OC 实现过程可知,OC 调用 Web 实现过程也是一样的,只不过是互转了一下角色而已。
1、 预先定好一个 handlerName
2、 Web端以handlerName注册一个任务,以备OC调用
3、OC端根据handlerName调用Web的bridge
4、剩下就是两个bridge之间的交流了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容