网站首页 > 技术文章 正文
今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stringByEvaluatingJavaScriptFromString函数
。现在主要是了解js是如何调用oc方法的,分享下探究过程。
源码不多,就一个头文件WebViewJavascriptBridge.h和实现文件WebViewJavascriptBridge.m, 和一个js文件,实现在js那边可以调用oc方法,也可以在oc里面调用js方法。
先上图,实现简单的oc和js互相调用的demo, 另外附加一个模拟项目中用到的oc和js互相调用场景:
一、然后说说js调用oc方法的原理,它们是如何实现的?库文件三个
我们跟踪下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
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__'
messagingIframe是个iframe标签,点击我们自定义html按钮标签,触发js事件,最后进入callHandler --> _doSend ,
当messagingIframe标签src重新赋值时,会触发UIWebView的代理方法(src的值一直是:wvjbscheme://__WVJB_QUEUE_MESSAGE__ ,也可自定义,这个在进入oc UIWebView代理方法时会用来作为判断标识)。
跟踪后面执行的过程:
至此,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下载
另外记录一个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];
猜你喜欢
- 2024-10-18 前端反向代理(前端反向代理怎么配置)
- 2024-10-18 JavaScript 九种跨域方式实现原理
- 2024-10-18 Proxy 来代理 JavaScript 里的类(js中proxy)
- 2024-10-18 octokit.js:2023每个前端都值得学习的通用SDK!
- 2024-10-18 一面 2:JS-Web-API 知识点与高频考题解析
- 2024-10-18 使用reveal.js制作精美的网页版PPT
- 2024-10-18 es6中的代理-Proxy(es proxy)
- 2024-10-18 事件——《JS高级程序设计》(javascript高级程序设计 javascript权威指南)
- 2024-10-18 「JavaScript 从入门到精通」18.WebApi介绍
- 2024-10-18 day7:前端面试题(js)(前端面试题2021及答案js)
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)