使用JavaScriptCore在JS和OC间通信
2016-06-27 13:21
453 查看
来源:cielpy 投稿
iOS 开发中,我们时不时的需要加载一些 Web 页面,一些需求使用 Web 页面来实现可以更可控,如上线后也可以发布更新,修改 UI 布局,或者修复 bug,这些 Web 页面的作用不止是展示,很大一部分是需要和原生代码实现的 UI 和业务逻辑发生交互的,那么不可避免的,就需要用一些方法来实现 Web 页面(主要是 JavaScript)和原生代码之间的通信,在 JavaScriptCore 出现之前,很多项目都在用 WebViewJavascriptBridge 作为
Web 页面和原生代码之间的一个桥梁(bridge),来传输一些数据和方法的调用,如 Facebook Messenger,Facebook Paper 等。
WebViewJavascriptBridge 原理简述
WebViewJavascriptBridge 的原理是通过自定义scheme,在加载一个特定标识的URL(
wvjbscheme://__BRIDGE_LOADED__)时在 UIWebView 的代理方法
webView:shouldStartLoadWithRequest:navigationType:中拦截 URL 并通过 UIWebView 的
stringByEvaluatingJavaScriptFromString:方法执行一段 JS,这个
JS 文件中声明了一些变量和方法,在通讯中作为一个桥梁,那么怎么通讯呢?
JS 中调用 OC 的方法
在 OC 中,实例化一个WebViewJavascriptBridge并调用
registerHandler:handler:注册并监听一下事件,第一个参数是一个字符串,用来标识一个特定的事件,
handler是一个 block,方法内部将标识作为
key,
handler作为值保存。
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler { _base.messageHandlers[handlerName] = [handler copy]; }
当 JS 中需要调用 OC 的方法时,组装一个类似结构的数据,一个字符串作为标识,将需要传输的数据作为值并保存在一个全局数组中
var sendMessageQueue = []; function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message['callbackId'] = callbackId; } // 主要就是这一行,将 message 保存到全局数组,供待会儿查询 sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
并触发一个特定的 URL(
wvjbscheme://__WVJB_QUEUE_MESSAGE__),UIWebView 则在
webView:shouldStartLoadWithRequest:navigationType:中拦截这个 URL,并执行一段 JS(
WebViewJavascriptBridge._fetchQueue();)
function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; return messageQueueString; }
查询 JS 中全局数组中的值,并转成 JSON 字符串返回,OC 中拿到 JSON 字符串,并解析,得到一个数组,遍历数组,根据数组中每个对象的
handlerName查询 OC 中是否有注册这个事件,如果有注册,则根据
handlerName取出保存在字典中的 block,并执行这个 block,block 可以接收一个 id 类型的参数,将 JS 全局数组中根据
handlerName取出来的数据作为参数传入
block。这样就实现了从 JS 到 OC 中的数据传输。
OC 中调用 JS 的方法
OC 中调用 JS 的方法相对简单,因为 UIWebView 可以主动执行 JS,JS 中可以将需要监听的事件注册,同样是字符串作为标识,一个函数作为值,保存到一个全局对象中,在 OC 中主动执行特定的 JS 方法时,将数据封装成 JSON 字符串,传入标识符和数据,并遍历 JS 中保存handler的全局对象,看有没有注册相应的事件,如果有,根据 事件的名字得到一个函数并执行。实现了 OC 调用 JS 中的方法并向 JS
中传输数据。
JavaScriptCore 时代的通讯
iOS 7 开始,苹果提供了一个叫作 JavaScriptCore 的框架,使用 JavaScriptCore 框架可以实现 OC 和 JS 的互相调用,而不需要依赖「桥」来实现,怎么通讯呢?JavaScriptCore 中 OC 调用 JS 方法
在 JS 中定义一个方法function alertFunc() { window.alert("这是一个JS中的弹框!") }
在
webViewDidFinishLoad:代理方法中,获取到
JSContext对象
- (void)webViewDidFinishLoad:(UIWebView *)webView { JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; [context setExceptionHandler:^(JSContext *ctx, JSValue *expectValue) { NSLog(@"%@", expectValue); }]; self.context = context; }
在一个 button 的点击事件中可以根据 JS 定义的方法的名字获得一个 JSValue 类型对象,这个对象就是在 JS 中定义的方法,JSValue 对象通过调用
callWithArguments:方法,执行这个 JS 方法。
- (IBAction)buttonClick:(UIButton *)sender { if (!self.context) { return; } JSValue *funcValue = self.context[@"alertFunc"]; [funcValue callWithArguments:nil]; }
点击按钮时,效果如下。
实现了 OC 中调用 JS 的方法。
JS 调用 OC 中的方法
在 OC 中,通过给 JSContext 的一个key赋值,值为一个 block,
key是 JS 中调用的方法的名字,代码如下:
self.context[@"ocAlert"] = ^{ // block 异步执行,如果涉及到 UI 的操作需要回到主线程操作 dispatch_async(dispatch_get_main_queue(), ^{ __strong typeof(weakSelf) strongSelf = weakSelf; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:@"这是OC中的弹框!" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [alert dismissViewControllerAnimated:YES completion:^{ }]; }]]; [strongSelf.navigationController presentViewController:alert animated:YES completion:nil]; }); };
在 Web 页面中创建一个 button 并设置 button 的 onClick 事件调用
ocAlert方法
点击这里
点击 Web 页面上的 button 按钮,效果如下
实现了 JS 调用 OC 中的方法。
是不是方便了很多?
写在后面
嗯 ,一篇文章应该有个写在后面的。以上当然只是 JavaScriptCore 框架的一个很小的应用,使用 JavaSciptCore 框架结合 Objective-C 的动态性可以做很多事,比如著名的热修复框架 JSPatch 就是这两者的结合。这里只是演示了 JS 和 OC 之间的方法调用,并没有传输数据,JavaScriptCore
框架是很容易的实现两者之间的数据传输的。具体做法可以参考参考资料。
苹果添加的这些新特性可以给开发带来很多便利,就是不知道有坑没有,嗯,且爬且珍惜吧。
使用 JavaScriptCore 进制通讯的 demo 放到了 GitHub,地址如下:
https://github.com/cielpy/CPYJSCoreDemo
参考资料
JavaScriptCore by ExampleJavaScriptCore初探
WebViewJavascriptBridge
相关文章推荐
- moment.js的汉化显示
- JavaScript 中 Property 和 Attribute 的区别详解
- javascript的api设计原则
- javascript self = this
- asp.net及javascript判断是否手机访问的方法
- javascript贪吃蛇小游戏
- 项目解析- JspLibrary - part2
- JS正则表达式验证数字代码
- JSP 自定义标签
- Javascript创建对象的七种模式(你必须掌握!)
- [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合
- Javascript异步编程的4种方法
- Jsp与servlet的区别
- JavaScript js闭包所用的场合以及优缺点
- arcgis api for javascript(一)使用自定义数据对FeatureLayer进行渲染
- JavaScript在苹果系统日期格式问题
- JSON 和 List之互相转换
- API例子:用Java/JavaScript下载内容提取器
- jsp跳转到servlet的路径问题
- JavaScript事件详细讲解