iOS中JavaScriptCore的简单介绍和使用
移动终端的应用按照技术的实现可分为三大类:本地化应用(Native App
),基于web的Web App
,和混合型应用(Hybrid App
)。由于HTML5的出现,它的跨平台和相对于原生应用的廉价等优势,使得现在越来越多的个人开发者或公司也青睐于使用H5来构建自己的移动应用。接下来介绍的是介于Native App和Web App之间的Hybrid App,在iOS应用开发中,可使用UIWebView(WKWebView)来加载一个网页,在iOS设备上渲染HTML网页和运行JavaScript代码,而且在iOS7之后引入的JavaScriptCore,提供了原生应用与Web页面的功能更强大,使用更简单的通信,只要很少的代码即可实现原生代码与JavaScript的相互调用。
三类App的对比
首先,还是要对比下三种App的优劣,这里只是用表格来简单说明一下:
Web App(网页应用) | Hybrid App(混合应用) | Native App(原生应) | |
---|---|---|---|
跨平台 | 优 | 优 | 差 |
开发成本 | 低 | 中 | 高 |
体验 | 差 | 中 | 优 |
Store获取market认可 | 不认可 | 认可 | 认可 |
安装 | 不需要 | 需要安装 | 需要安装 |
维护更新 | 简单 | 简单 | 复杂 |
由表中可看出各有优劣,开发者应该根据自身的情况来选择适合自己的方式来构建应用。
JavaScriptCore的介绍
在iOS7之前,我们只能通过向UIWebView发送stringByEvaluatingJavaScriptFromString:
消息来执行一段JavaScript脚本。而如果想在JavaScript调用Objective-C代码,必须打开一个自定义的URL(例如:foo://),然后在UIWebView的delegate方法webView:shouldStartLoadWithRequest:navigationType中进行处理。在OS X Mavericks和iOS 7引入了JavaScriptCore库,它把WebKit的JavaScript引擎用Objective-C封装,提供了简单、快速以及安全的方式接入世界上最流行的语言-JavaScript。JavaScriptCore是封装了JavaScriptCore与Objective-C桥接的API,可以轻松实现JS与OC的互调。
利用JavaScriptCore,可以实现的功能:
- 在OC和JS之间可以无缝的传递值或对象;
- 编写JS调用OC的代码时,可以使用现代的OC语法,如Blocks、下标等;
- 创建混合对象,如原生对象可以将JS的一个属性或函数作为一个属性,如JS的json,OC可通过toDictionary创建一个原生字典对象;
利用OC与JS结合开发的好处:
-
实现快速开发
如App里面的某个模块的业务需求变化得非常频繁,或者在某个功能复复杂的模块中想新增一个复杂的功能模块,那么我们可以考虑使用H5和JavaScript进行开发,这样比使用Objective-C开发效率更高,后期的维护更方便。 -
团队职责划分明确
可以明确的划分工作职责,H5和JavaScript开发能力较强的,专门负责开发web页面,原生App开发的开发人员专门负责原生App的开发,两者开发后,后期进行联调工作。 -
JS的解释型特性
由于JavaScript是一种解释型的语言,修改了js代码后,可以马上看到效果。 -
跨平台(编写一次,多平台运行)
具有跨平台的好处,JavaScript只需编写一次,可运行与多个平台(如iOS和Android)。
JavaScriptCore的使用
首先,开始使用JavaScriptCore框架,在Xcode中导入framework,然后在相关类中引入以下头文件:
JavaScriptCore/JavaScriptCore.h
该头文件中包含了以下的常用的头文件:
JSContext.h
JSValue.h
JSManagedValue.h
JSVirtualMachine.h
JSExport.h
JSContext:是运行JavaScript代码的环境,需要用它来执行JavaScript代码,一个JSContext就是一个全局环境的变量,相当于window。我们可以很容易的利用JSContext来创建JS变量、函数等。
JSValue:表示对应于JavaScript中原始的很多值,如boolean,array,string等,通过一系列方法去访问到其可能的值,以保证有正确的Foundation值。如JS的array,通过JSValue的toArray方法就可以转换为Objective-C的NSArray类型,JS的json,通过JSValue的toDictionary方法,可转换为OC中的NSDictionary字典类型的数据等等,其他的类型都是类似的;
JSExport协议:实现这个协议,可以将我们定义的方法、属性等都自动地提供给JavaScript代码,可以在JS代码中直接使用我们自定义的原始代码;
JSVirtualMachin:字面上可以看出,它就像是虚拟机一样,拥有自己的堆结构和垃圾回收机制,大多数情况下不会和它交互,除非一些涉及内存管理和多线程的处理的情况,为JavaScript的允许提供底层资源。
JSManagedValue
OC和swift的对象都是使用引用计数进行管理,而JavaScript的是垃圾回收机制。为了避免两种语言交互使用时产生循环引用的问题,需要使用JSManagedValue
来进行内存辅助管理。
JScontext / JSValue
可通过以下方式来创建一个全局的JSContext环境,并利用它来执行js代码:
/// 创建一个JSContext实例,只需创建一次
///JSContext *context = [[JSContext alloc] init];//有问题,内置的函数都不会执行
/// 每个UIWebView都有自己的JSContext实例,使用KVC获取JSContext实例
self.context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context evaluateScript:@"var num1 = 5 + 5;"];
[context evaluateScript:@"var arr = ['a','b','c']"];
[context evaluateScript:@"var triple = function(value){return value * 5}"];//定义一个函数
JSValue *returnValue = [context evaluateScript:@"triple(num1);"];//返回一个JSValue值
任何出自 JSContext 的值都被包裹在一个 JSValue 对象中。像 JavaScript 这样的动态语言需要一个动态类型,所以 JSValue 包装了每一个可能的 JavaScript 值:字符串和数字;数组、对象和方法;甚至错误和特殊的 JavaScript 值诸如 null 和 undefined。
异常捕获
当我们调用JS时,可能会出现错误,这时我们需要追踪和记录出现的语法、类型和运行时的错误,通过设置context上下文的exceptionHandler属性来完成,如下所示,exceptionHandler是一个接受JSContext本身的引用及exception异常信息的回调处理。
/// 处理异常
self.context.exceptionHandler = ^(JSContext *context,JSValue *exception){
NSLog(@"exception:%@",exception);
/// 通过js alert,提示错误信息
NSString *errorFunc = [NSString stringWithFormat:@"alert(\"%@\");",[exception toString]];
[weakSelf.context evaluateScript:errorFunc];
};
JavaScript调用OC
上面我们是通过JSContext实例来在原生代码中调用JavaScript,现在我们来看如何实现在JavaScript中调用原生代码。实现的方式有两种:Blocks
和JSExport协议
。
Blocks
context里的一个标识符,如getCurSystemVersionOnly,就是对应JavaScript的一个函数名,将OC中的block赋值给context中的一个标识符,JavaScriptCore就自动把block封装在JavaScript的函数里,也就转换成了js的方法,当在JavaScript中调用时,直接调用对应的原生方法。
self.context[@"getCurSystemVersionOnly"] = ^(NSString *callBack){
NSString *curVerison = [[UIDevice currentDevice] systemVersion];
/// 获取传递的参数,也可以直接在block中获取
NSArray *args = [JSContext currentArguments];
JSContext *currentContext = [JSContext currentContext];//注意不要用self.context,防止强引用循环
if (args != nil && [args count] != 0) {
/// 获取回调函数,需要截取函数名
/*
function callBack(version){
alert(version);
}
*/
weakSelf.getVersionCallBack = [weakSelf truncateFuncName:[[args objectAtIndex:0] toString]];
/// 执行js中的回调函数来传值
if (weakSelf.getVersionCallBack) {
[[[JSContext currentContext] globalObject] invokeMethod:weakSelf.getVersionCallBack withArguments:[NSArray arrayWithObjects:curVerison, nil]];
}
}
};
/// 另声明一个函数,给js调用,以便js可以自定义更多内容后再调用这个接口
[self.context evaluateScript:@"globalObj.getCurSystemVersion = function(callBack){getCurSystemVersionOnly(callBack);}"];
除了使用context[@""]=block的方式,也可使用以下的方式:
void (^block)() = ^(NSString *string) {
NSLog(@"%@", string);
};
/// 通过setObject:forKeyedSubscript的方法
[self.context setObject:block forKeyedSubscript:@"print"];
JSExport协议
JSExport是一个协议,它可以让原生的属性、方法直接称为JavaScript的属性或者方法,将原生的对象全部导出当成JavaScript的对象使用。
首先定义一个继承自JSExport的协议(不可直接使用它),然后在JavaScript调用的类中实现这个协议。
///1. 定义一个继承自JSExport的协议
@protocol MJwebViewJSExport <JSExport>
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *password;
- (void)print:(NSString *)message;
@end
然后在使用JavaScript的类中实现:
///2. 让类遵守协议
@interface MJwebBaseViewController () <UIWebViewDelegate,MJwebViewJSExport>
///4. 将本类的句柄复制给context,JS调用时就可使用这个句柄进行调用
self.context[@"JYObject"] = self;
///5. 直接js调用
[self.context evaluateScript:@"JYObject.print('hello guy!')"];
#pragma mark - MJwebViewJSExport
///3. 实现协议方法
- (void)print:(NSString *)message {
NSLog(@"message:%@",message);
}
JavaScript中的调用:
function useJSExport(value){
//直接调用
JYObject.print("我是测试的值!");
}
小结
以上只是简单的介绍下JavaScriptCore的基本使用,没有涉及到太多的内存管理的知识,JSManagedValue的使用等都没有具体介绍。iOS开发者除了要知道JavaScriptCore的使用外,对JavaScript的相关知识也要有一定的了解,这样才能更好的完成js接口的开发,使更加符合需求。