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互相调用场景:
一、然后说说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 = 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=; 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)
})();
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>
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下载
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];
另外记录一下:UIWebView不能监听加载的html页面a标签进行相对链接跳转。
举例说明,比如加载的html页面有个a标签链接:
<a href="/index.html">去首页</a>
这种跳转UIWebViewDelegate的代理方法监听不到
原文链接:http://www.cnblogs.com/tandaxia/p/5699886.html
WebViewJavascriptBridge源码探究--看OC和JS交互过程的更多相关文章
- [源码分析] 从源码入手看 Flink Watermark 之传播过程
[源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...
- OC与JS交互前言
OC与JS交互过程中,可能会需要使用本地image资源及html,css,js文件,这些资源应该如何被加载? 一.WebView加载HTML UIWebView提供了三个方法来加载html资源 1. ...
- OC与JS交互之WebViewJavascriptBridge
上一篇文章介绍了通过UIWebView实现了OC与JS交互的可能性及实现的原理,并且简单的实现了一个小的示例DEMO,当然也有一部分遗留问题,使用原生实现过程比较繁琐,代码难以维护.这篇文章主要介绍下 ...
- Vue源码探究-虚拟DOM的渲染
Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue ...
- spring-cloud-sleuth+zipkin源码探究
1. spring-cloud-sleuth+zipkin源码探究 1.1. 前言 粗略看了下spring cloud sleuth core源码,发现内容真的有点多,它支持了很多类型的链路追踪, ...
- spring-boot-2.0.3之quartz集成,数据源问题,源码探究
前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...
- Vue源码探究-全局API
Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...
- Vue源码探究-事件系统
Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
随机推荐
- [转载]MVVM模式原理分析及实践
没有找到很好的MVVM模式介绍文章,简单找了一篇,分享一下.MVVM实现了UI\UE设计师(Expression Blend 4设计界面)和软件工程师的合理分工,在SilverLight.WPF.Wi ...
- 2000条你应知的WPF小姿势 基础篇<63-68 Triggers和WPF类逻辑结构>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000ThingsYou Should Know About C# 和 2,00 ...
- [NodeJS] Hello World 起步教程
概述: 做数据,免不了需要展示数据,数据可视化是必须经历的步骤. 本文将提供一个NodeJS的起步教程,是笔者这两天探索的小结. 正文: 1. 为什么使用NodeJS 究竟是以B/S还是C/S的架构 ...
- ABP(现代ASP.NET样板开发框架)系列之5、ABP启动配置
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之5.ABP启动配置 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...
- ABP源码分析十:Unit Of Work
ABP以AOP的方式实现UnitOfWork功能.通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候,一并添加到该类 ...
- jQuery选择器中,通配符[id^='code']input[id$='code'][id*='code']
1.选择器 (1)通配符: $("input[id^='code']");//id属性以code开始的所有input标签 $("input[id$='code']&qu ...
- WCF学习之旅—第三个示例之五(三十一)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) WCF学习 ...
- .NET Core采用的全新配置系统[7]: 将配置保存在数据库中
我们在<聊聊默认支持的各种配置源>和<深入了解三种针对文件(JSON.XML与INI)的配置源>对配置模型中默认提供的各种ConfigurationSource进行了深入详尽的 ...
- C#设计模式:单件(例)模式 -- 类也玩计划生育
这里写的代码,相当于<Head First 设计模式>的读书笔记,原书是java的,自己在学习的过程中将其翻译为C#: (一)剖析经典的单件模式实现 单件模式 -- 确保一个类只有一个实例 ...
- [.NET] WebApi 生成帮助文档及顺便自动创建简单的测试工具
==========最终的效果图========== ==========下面开始干活:生成帮助文档========== 一.创建 WebApi 项目 二.找到 HelpPageConfig.cs 并 ...