Objc与JS间相互调用
2015-06-26 10:21
405 查看
过去3、4年都在进行跨平台的混合应用开发,但一直没有系统梳理跨平台技术的底层原理,趁新工作未正式入职,这里整理一下。
跨平台的一种实现是基于webview。所谓webview,实质是在原生app中打开一个内嵌浏览器,具体到iOS平台就是使用UIWebView这个控件。然后就很容易理解了,我们相当于开发一个webapp(网页应用),然后通过原生应用作为用户入口(而非原生浏览器),用户会访问到远程服务器的网页内容。从用户感知上,似乎是在使用一个App,但实际上是在访问一个网页。
以上,只是跨平台基于webview实现的工作原理,而更重要的是如何桥接webview的js和app的objc,使得webapp也可用使用原生的功能api,如调用摄像头等,而app又可以调用webview里的js,即向双通信。
Apple开放了一个叫做JavascriptCore的框架,此框架最早在OSX10.2就存在,但到了2013年在OSX10.9上才发布其调用的API,而后又在iOS7上公开,由此我们可用名正言顺地使用了。
JavascriptCore提供了以下几个API,实现跨平台通信:
JavascriptCore/API/JSContext.h
JavascriptCore/API/JSExport.h
JavascriptCore/API/JSValue.h
JSContext是JavascriptCore的主入口,它代表了JS的运行时环境,在其中可以定义对象、方法等,这些实体(对象、方法)的生命周期在JSContext被释放的时候才结束。而且可用指定的JSVirtualMachine来创建JSContext,每个JSVM都会独立运行在一个线程上。
我们可用通过JSContext的evaluateScript方法来定义我们的JS方法,而且是通过字符串定义代码,当然可以通过读取外边js文件来实现。看下面的例子:
这里,相当于在objc层,向JSContext注入了一个isValidNumber的js方法。
正如上文所述,JSContext代表了一个JS运行环境,而我们的示例代码都是单独创建这个JSContext运行环境的,实际上UIWebView实例也有它自己的JSContext运行环境。为了修改web上的内容,我们需要访问UIWebView的JSContext。
但Apple就是一个闷骚男,虽然已经公开了JavascriptCore的API,但又不提供直接访问UIWebView’s JSContext的方法。
幸好“key-value”把我们救了回来:
然后,我们可以通过JSValue来获取JSContext中js方法的引用和执行的结果:
JavascriptCore会自动转换JSValue的对象类型,比如这里isValidNumber返回的boolean,同时还支持NSString, NSDate, NSDictionary, NSArray等。
另外,我们还可以增加异常捕捉
再有,通过JSExport可以将objc的方法暴露给JS。
addContact这个方法是在BNRContactAppJS协议中声明的,BNRContactAppJS又源自于JSExport,所以addContact方法将会暴露给JS环境,而其他方法则对JS环境而言是隐藏的。
最后,我们看一个完整的例子
最终跨平台调用就在这一句:
在JS环境调用了objc的方法。
总结一下:
==从objc调用js:JSContext的evaluateScriptf方法和JSValue的callWithArguments方法;==
==捕捉JS执行的异常;==
==从WebView实例获取JSContext;==
==通过JSExport将objc方法暴露给js调用。==
最后啰嗦一下,iOS7以前,并没有JavascriptCore,所以多使用 stringByEvaluatingJavaScriptFromString。
Titanium 就是使用了JavascriptCore的方式。
跨平台的一种实现是基于webview。所谓webview,实质是在原生app中打开一个内嵌浏览器,具体到iOS平台就是使用UIWebView这个控件。然后就很容易理解了,我们相当于开发一个webapp(网页应用),然后通过原生应用作为用户入口(而非原生浏览器),用户会访问到远程服务器的网页内容。从用户感知上,似乎是在使用一个App,但实际上是在访问一个网页。
以上,只是跨平台基于webview实现的工作原理,而更重要的是如何桥接webview的js和app的objc,使得webapp也可用使用原生的功能api,如调用摄像头等,而app又可以调用webview里的js,即向双通信。
Apple开放了一个叫做JavascriptCore的框架,此框架最早在OSX10.2就存在,但到了2013年在OSX10.9上才发布其调用的API,而后又在iOS7上公开,由此我们可用名正言顺地使用了。
JavascriptCore提供了以下几个API,实现跨平台通信:
JavascriptCore/API/JSContext.h
JavascriptCore/API/JSExport.h
JavascriptCore/API/JSValue.h
JSContext是JavascriptCore的主入口,它代表了JS的运行时环境,在其中可以定义对象、方法等,这些实体(对象、方法)的生命周期在JSContext被释放的时候才结束。而且可用指定的JSVirtualMachine来创建JSContext,每个JSVM都会独立运行在一个线程上。
我们可用通过JSContext的evaluateScript方法来定义我们的JS方法,而且是通过字符串定义代码,当然可以通过读取外边js文件来实现。看下面的例子:
// getting a JSContext JSContext *context = [JSContext new]; // defining a JavaScript function NSString *jsFunctionText = @"var isValidNumber = function(phone) {" " var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;" " return phone.match(phonePattern) ? true : false;" "}"; [context evaluateScript:jsFunctionText];
这里,相当于在objc层,向JSContext注入了一个isValidNumber的js方法。
正如上文所述,JSContext代表了一个JS运行环境,而我们的示例代码都是单独创建这个JSContext运行环境的,实际上UIWebView实例也有它自己的JSContext运行环境。为了修改web上的内容,我们需要访问UIWebView的JSContext。
但Apple就是一个闷骚男,虽然已经公开了JavascriptCore的API,但又不提供直接访问UIWebView’s JSContext的方法。
幸好“key-value”把我们救了回来:
// get JSContext from UIWebView instance JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
然后,我们可以通过JSValue来获取JSContext中js方法的引用和执行的结果:
// 获取isValidNumber方法的引用 JSValue *jsFunction = context[@"isValidNumber"]; // 通过callWithArguments方法调用js方法 JSValue *value = [jsFunction callWithArguments:@[ phone ]];
JavascriptCore会自动转换JSValue的对象类型,比如这里isValidNumber返回的boolean,同时还支持NSString, NSDate, NSDictionary, NSArray等。
另外,我们还可以增加异常捕捉
[context setExceptionHandler:^(JSContext *context, JSValue *value) { NSLog(@"%@", value); }];
再有,通过JSExport可以将objc的方法暴露给JS。
@protocol BNRContactAppJS <JSExport> - (void)addContact:(BNRContact *)contact; @end @interface BNRContactApp : NSObject <BNRContactAppJS> ... @end
addContact这个方法是在BNRContactAppJS协议中声明的,BNRContactAppJS又源自于JSExport,所以addContact方法将会暴露给JS环境,而其他方法则对JS环境而言是隐藏的。
最后,我们看一个完整的例子
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// get JSContext from UIWebView instance JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// enable error logging
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(@"WEB JS: %@", value);
}];
// give JS a handle to our BNRContactApp instance
context[@"myApp"] = self.app;
// register BNRContact class
context[@"BNRContact"] = [BNRContact class];
// add function for processing form submission
NSString *addContactText =
@"var contactForm = document.forms[0];"
"var addContact = function() {"
" var name = contactForm.name.value;"
" var phone = contactForm.phone.value;"
" var address = contactForm.address.value;"
" var contact = BNRContact.contactWithNamePhoneAddress(name, phone, address);"
" myApp.addContact(contact);"
"};"
"contactForm.addEventListener('submit', addContact);";
[context evaluateScript:addContactText];
}
最终跨平台调用就在这一句:
myApp.addContact(contact);
在JS环境调用了objc的方法。
总结一下:
==从objc调用js:JSContext的evaluateScriptf方法和JSValue的callWithArguments方法;==
==捕捉JS执行的异常;==
==从WebView实例获取JSContext;==
==通过JSExport将objc方法暴露给js调用。==
最后啰嗦一下,iOS7以前,并没有JavascriptCore,所以多使用 stringByEvaluatingJavaScriptFromString。
Titanium 就是使用了JavascriptCore的方式。
相关文章推荐
- jsp 页面中 判断session是否失效
- jstree 取消选中父节点
- 用gson处理json时bean和json串的对应关系
- javascript变量初始化位置
- javascript实现倒计时(精确到秒)
- js计算2个日期之间相差天数
- href=“#”与href="javascript:void(0)" 的区别
- Extjs sencha cmd打包压缩部署 前台代码压缩
- 详解JavaScript立即执行函数表达式
- arguments对象验证函数的参数是否合法
- Why we made vorlon.js and how to use it to debug your JavaScript remotely
- JavaScript + CSS3 实现的海报画廊特效
- javascript规范
- Truthy Vs Falsy Values in JavaScript
- 02 js鼠标点击事件在各个浏览器中的写法及Event对象属性介绍
- JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解
- js判断是否为数组的函数: isArray()
- 自动适应屏幕宽度
- JavaScript + CSS3 实现的海报画廊特效
- javascript中外部js文件取得自身完整路径得办法