转载

WebViewJavascriptBridge源码探究--看OC和JS交互过程

今天把实现OC代码和JS代码交互的第三方库 WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了 stringByEvaluatingJavaScriptFromString函数

。现在主要是了解js是如何调用oc方法的,分享下探究过程。

源码不多,就一个头文件WebViewJavascriptBridge.h和实现文件WebViewJavascriptBridge.m, 和一个js文件,实现在js那边可以调用oc方法,也可以在oc里面调用js方法。

先上图,实现简单的oc和js互相调用的demo, 另外附加一个模拟项目中用到的oc和js互相调用场景:

WebViewJavascriptBridge源码探究--看OC和JS交互过程

一、然后说说js调用oc方法的原理,它们是如何实现的?库文件三个

WebViewJavascriptBridge源码探究--看OC和JS交互过程

我们跟踪下oc控制器加载UIWebView的过程和js调用oc方法过程

1、程序启动,在自定义控制器里,创建一个 WebViewJavascriptBridge对象时,会加载 WebViewJavascriptBridge.js.txt文件,里面是初始js代码

在这个js里面,创建了一个 WebViewJavascriptBridge脚本对象,另外创建一个隐藏的iframe标签:每次js调用oc方法,都是修改iframe标签的src来触发UIWebView的代理监听方法

  ;(function() {     if (window.WebViewJavascriptBridge) { return }     var messagingIframe     var sendMessageQueue = []     var receiveMessageQueue = []     var messageHandlers = {}          var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'     var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'          var responseCallbacks = {}     var uniqueId = 1          function _createQueueReadyIframe(doc) {         messagingIframe = doc.createElement('iframe')         messagingIframe.style.display = 'none'         messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE         doc.documentElement.appendChild(messagingIframe)     }      function init(messageHandler) {         if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') }         WebViewJavascriptBridge._messageHandler = messageHandler         var receivedMessages = receiveMessageQueue         receiveMessageQueue = null         for (var i=0; i<receivedMessages.length; i++) {             _dispatchMessageFromObjC(receivedMessages[i])         }     }      function send(data, responseCallback) {         _doSend({ data:data }, responseCallback)     }          function registerHandler(handlerName, handler) {         messageHandlers[handlerName] = handler     }          function callHandler(handlerName, data, responseCallback) {         _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); //将字典放入数组         //修改iframe标签的src属性,UIWebView监听执行代理方法         messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;     }      function _fetchQueue() {  //json数组转成json字符串         var messageQueueString = JSON.stringify(sendMessageQueue)         sendMessageQueue = []         return messageQueueString     }      function _dispatchMessageFromObjC(messageJSON) {         setTimeout(function _timeoutDispatchMessageFromObjC() {             var message = JSON.parse(messageJSON)             var messageHandler                          if (message.responseId) {                 var responseCallback = responseCallbacks[message.responseId]                 if (!responseCallback) { return; }                 responseCallback(message.responseData)                 delete responseCallbacks[message.responseId]             } else {                 var responseCallback                 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)                     }                 }             }         })     }          function _handleMessageFromObjC(messageJSON) {         if (receiveMessageQueue) {             receiveMessageQueue.push(messageJSON)         } else {             _dispatchMessageFromObjC(messageJSON)         }     }      window.WebViewJavascriptBridge = {         init: init,         send: send,         registerHandler: registerHandler,         callHandler: callHandler,         _fetchQueue: _fetchQueue,         _handleMessageFromObjC: _handleMessageFromObjC     }      var doc = document     _createQueueReadyIframe(doc)     var readyEvent = doc.createEvent('Events')     readyEvent.initEvent('WebViewJavascriptBridgeReady')     readyEvent.bridge = WebViewJavascriptBridge     doc.dispatchEvent(readyEvent) })();  View Code

WebViewJavascriptBridge源码探究--看OC和JS交互过程

WebViewJavascriptBridge源码探究--看OC和JS交互过程 WebViewJavascriptBridge源码探究--看OC和JS交互过程

2、UIWebView加载我们自定义的html页面TestJSBridge.html, 里面有脚本注册js调用oc方法标识,和oc调用js标识

  <html>     <head>         <meta charset="utf-8"/>         <style type="text/css">             html { font-family:Helvetica; color:#222; background:#D5FFFD; border: 5px dashed blue;}             .rowH3{margin: 0px; text-align: center;}             .jsBtn{font-size: 18px;}         </style>     </head>     <body>         <h3 class="rowH3">测试OC和JS互相调用</h3>         <button class="jsBtn" id="jsBtn">JS调用OC方法</button>         <div id="logDiv"><!-- 打印日志 --></div>     </body> </html> <script type="text/javascript">     window.onerror = function(err) {         printLog(err);     }          function connectWebViewJavascriptBridge(callback) {         if (window.WebViewJavascriptBridge) {             callback(WebViewJavascriptBridge);         } else {             document.addEventListener('WebViewJavascriptBridgeReady', function() {                 callback(WebViewJavascriptBridge);             }, false);         }     }          var uniqueId = 1;     //日志打印方法     function printLog(data) {         var logObj = document.getElementById('logDiv');         var el = document.createElement('div');         el.className = 'logLine';         el.innerHTML = uniqueId++ + ': ' + JSON.stringify(data); //json转字符串         if (logObj.children.length) { logObj.insertBefore(el, logObj.children[0]) }         else { logObj.appendChild(el) }     }          //初始化调用函数connectWebViewJavascriptBridge     connectWebViewJavascriptBridge(function(bridge) {                  bridge.init(function(message, responseCallback) {});                                               //注册js响应方法,响应OC调用,标识objc_Call_JS_Func         bridge.registerHandler('objc_Call_JS_Func', function(data, responseCallback) {             printLog(data);  //打印oc传过来的参数         });          //给标签按钮设置点击事件         var callbackButton = document.getElementById('jsBtn');         callbackButton.onclick = function(e) {             e.preventDefault();             //注册标识js_Call_Objc_Func,便于js给IOS发送消息             bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我从js那边过来!'},  function(response) { });         }     });      </script>  View Code

3、点击html标签按钮,触发js事件

//给标签按钮设置点击事件   var callbackButton = document.getElementById('jsBtn');   callbackButton.onclick = function(e) {    e.preventDefault();             //注册标识js_Call_Objc_Func,便于js给IOS发送消息    bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我从js那边过来!'},  function(response) { });   }

我们跟踪bridge.callHandler方法,进入WebViewJavascriptBridge.js

var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'

var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

WebViewJavascriptBridge源码探究--看OC和JS交互过程

messagingIframe是个iframe标签,点击我们自定义html按钮标签,触发js事件,最后进入callHandler -->  _doSend ,

当messagingIframe标签src重新赋值时,会触发UIWebView的代理方法(src的值一直是:wvjbscheme:// __WVJB_QUEUE_MESSAGE__ ,也可自定义,这个在进入oc UIWebView代理方法时会用来作为判断标识)。

跟踪后面执行的过程:

WebViewJavascriptBridge源码探究--看OC和JS交互过程

WebViewJavascriptBridge源码探究--看OC和JS交互过程

WebViewJavascriptBridge源码探究--看OC和JS交互过程

WebViewJavascriptBridge源码探究--看OC和JS交互过程

至此,js调用oc成功

总结js调用oc过程:

-->   触发js事件

-->   把要传入参数和自定义注册标识“ js_Call_Objc_Func” 存入js数组 sendMessageQueue

-->  重新赋值iframe标签的src属性,触发UIWebView代理方法, 根据src的值进入相应处理方法中

-->    在oc方法里面调用js方法 _fetchQueue, 获取js数组里面所有的参数  

-->   根据传入的自定义注册标识 js_Call_Objc_Func  从oc字典 _messageHandlers找出匹配block, 最后执行block,里面有我们自定义处理的后续代码

二、oc调用js过程

从oc内部发起

-- > 调用bridge的 callHandler方法,传入需要的参数和自定义注册标识

--> 最后使用UIWebView系统方法 stringByEvaluatingJavaScriptFromString调用js脚本 WebViewJavascriptBridge._handleMessageFromObjC 完成参数的传递

---------------------------------------------  end --------------------------------------

DEMO下载

github地址: https://github.com/xiaotanit/Tan_WebViewJavaScriptBridge

另外记录一个UIWebView不能加载带中文参数的url问题:

假设加载url为: http://baidu.com/?search= 博客园

这样UIWebView加载这个带中文参数的url, 是不能显示的,需要把中文进行转义,才能显示。

使用字符串方法 stringByAddingPercentEncodingWithAllowedCharacters对中文进行转义

 NSString *str = @"http://baidu.com/?search=博客园";   //  str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];   str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}/"[]|//<> "].invertedSet];       NSURL *url = [NSURL URLWithString:str];   NSURLRequest *request = [NSURLRequest requestWithURL:url]; 
原文  http://www.cnblogs.com/tandaxia/p/5699886.html
正文到此结束
Loading...