JS和Native交互之 -WebViewJavascriptBridge源码分析

源码地址:https://github.com/marcuswestin/WebViewJavascriptBridge

1、对外接口

初始化OC初始化JS
[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview handler:(WVJBHandler)handler]
 
[WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview webViewDelegate:(UIWebViewDelegate*)webViewDelegate handler:(WVJBHandler)handler]
 
document.addEventListener('WebViewJavascriptBridgeReady', function onBridgeReady(event) { ... }, false)
 
bridge.init(function messageHandler(data, response) { ... })
 
OC发送消息to JSJS发送消息to OC
[bridge send:(id)data]
[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]
 
bridge.send("Hi there!")
bridge.send({ Foo:"Bar" })
bridge.send(data, function responseCallback(responseData) { ... })
 
OC注册事件(先)JS调用事件(后)
[bridge registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler]
 
WebViewJavascriptBridge.callHandler("handlerName")
 
OC调用事件(后)JS注册事件(先)
[bridge callHandler:(NSString*)handlerName data:(id)data]
[bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback]
 
bridge.registerHandler("handlerName", function(responseData) { ... })
 

三类API接口用于OC与JS之间交互:初始化接口;发送消息接口,并且可以添加发送消息完成的回调函数;事件注册和调用接口,需要先在一端注册事件,另一端可以根据事件名称调用函数

除了上述提到的外部方法:还有两个方法十分重要, JS部分最重要的内部方法: _handleMessageFromObjC;OC部分重要的内部方法:flushMessageQueue


2、类结构图


WebViewJavascriptBridge目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用时可以二选其一;
WebViewjavascriptBridgeBase是通用类,用于处理从Native到JS的消息注入,消息队列处理和分发,JSON数据的序列化和反序列化,LOG输出;

3、源码分析

3.1 消息发送JS-》Native

[bridge send:(id)data]
[bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback]

这两个函数最后都是调用_doSend({ 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
}

首先生成callbackId,由不断加1的唯一需要和时间戳构成,如果有responseCallback函数,使用 callbackId作为索引,存入responseCallbacks对象,等到从OC侧返回的信息中对应的callbackId与当前responseCallbacks中callbackId相同时,调用回调函数responseCallback;sendMessageQueue是个消息数组,每次的新消息放入其中,messagingIframe是iframe对象,当设置src产生一次请求,在OC端的
( BOOL)webView:( UIWebView *)webView shouldStartLoadWithRequest:( NSURLRequest *)request navigationType:( UIWebViewNavigationType)navigationType 会拦截请求内容

代码:
- ( 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 isCorrectProcotocolScheme:url]) {
         if ([ _base isCorrectHost: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;
    }
}

重点部分:执行注入 _evaluateJavascript,
OC
-( NSString *)webViewJavascriptFetchQueyCommand {
     return  @"WebViewJavascriptBridge._fetchQueue();";
}
JS
function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue)
    sendMessageQueue = []
   return messageQueueString
}
这个函数从JS的sendMessageQueue消息队列获取内容返回,这个sendMessageQueue是在之前的_doSend函数中传入的消息内容,也就是 NSString *messageQueueString = [ self _evaluateJavascript:[ _base webViewJavascriptFetchQueyCommand]];这句代码获得从JS侧拿到的数据内容,然后调用[ _base flushMessageQueue:messageQueueString];对消息分发处理

- ( void)flushMessageQueue:( NSString *)messageQueueString{
     id messages = [ self _deserializeMessageJSON:messageQueueString];
     if (![messages  isKindOfClass:[ NSArray  class]]) {
         NSLog( @"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messages class], messages);
         return;
    }
     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;
             if (message[ @"handlerName"]) {
                handler =  self. messageHandlers[message[ @"handlerName"]];
            }  else {
                handler =  self. messageHandler;
            }
           
             if (!handler) {
                [ NSException  raise: @"WVJBNoHandlerException"  format: @"No handler for message from JS: %@", message];
            }
           
            handler(message[ @"data"], responseCallback);
        }
    }
}

这个是整个框架中OC侧重要的函数,但是目前首先分析 消息发送JS-》Native涉及到的部分内容,返回的消息包含 callbackId,数据拼接后调用[ self _queueMessage:msg];发送回JS侧的数据改为 responseId为关键字key,具体如下:

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

self. startupMessageQueue只有首次启动时有效,之后为nil,所以都是走[ self _dispatchMessage:message];

- ( void)_dispatchMessage:( WVJBMessage*)message {
     NSString *messageJSON = [ self _serializeMessage:message];
    [ self  _log: @"SEND"  json:messageJSON];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\\" withString: @"\\\\"];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\"" withString: @"\\\""];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\'" withString: @"\\\'"];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\n" withString: @"\\n"];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\r" withString: @"\\r"];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\f" withString: @"\\f"];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\u2028" withString: @"\\u2028"];
    messageJSON = [messageJSON  stringByReplacingOccurrencesOfString: @"\u2029" withString: @"\\u2029"];
   
     NSString* javascriptCommand = [ NSString stringWithFormat: @"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
     if ([[ NSThread currentThreadisMainThread]) {
        [ self  _evaluateJavascript:javascriptCommand];

    }  else {
         dispatch_sync( dispatch_get_main_queue(), ^{
            [ self  _evaluateJavascript:javascriptCommand];
        });
    }
}
此函数对message特殊字符进行转义处理,然后执行JS注入语句, WebViewJavascriptBridge._handleMessageFromObjC执行到JS侧

这个是整个框架中JS侧重要的函数,用于处理从OC侧返回的消息
function _dispatchMessageFromObjC (messageJSON )  {
         setTimeout ( function _timeoutDispatchMessageFromObjC ( )  {
             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 ( { responseId :callbackResponseId , responseData :responseData  } )
                     }
                 }

                 var handler  = WebViewJavascriptBridge ._messageHandler
                 if  (message .handlerName )  {
                    handler  = messageHandlers [message .handlerName ]
                 }

                 try  {
                    handler (message .data , responseCallback )
                 }  catch (exception )  {
                     if  ( typeof console  !=  ' undefined ' )  {
                        console . log ( " WebViewJavascriptBridge: WARNING: javascript handler threw. " , message , exception )
                     }
                 }
             }
         } )
     }
执行JS侧本地回调函数


3.2 消息发送 OC--》JS
bridge.send("Hi there!")
bridge.send({ Foo:"Bar" })
bridge.send(data, function responseCallback(responseData) { ... })

调用
    [ _base  sendData:data responseCallback:responseCallback  handlerName: nil];

执行  _queueMessage

3.3 OC注册事件和JS调用

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

JS调用
function callHandler(handlerName, data, responseCallback) {
     _doSend({ handlerName:handlerName, data:data }, responseCallback)
}

加入handlerName和data数据传给OC侧,JS侧记录responseCallback,最后也会走到- ( void)flushMessageQueue:( NSString *)messageQueueString函数中,由于既没有callbackId也没有responseId,所以只处理handlerName及相关数据,最后走到 - ( void)flushMessageQueue:( NSString*)messageQueueString解析,OC侧执行之前注册的handler并传入data数据

3.4 JS注册事件和OC调用

JS注册
function registerHandler(handlerName, handler) {
     messageHandlers[handlerName] = handler
}
本地记录

OC调用
- ( 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];
}

handlerName和data  在 - ( void)sendData:( id)data responseCallback:( WVJBResponseCallback)responseCallback handlerName:( NSString*)handlerName中处理

4 、总结

1、与其它框架相比,此框架没有采用url拦截解析参数方式,而是多次JS注入参数获取,API接口暴露的操作在底层需要多次OC与JS之间交互完成
2、WKwebview部分没有使用新的delgate方法,而是沿用iframe方式,个人觉得采用新接口可能效率更高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值