JavaScriptCore是iOS 7之后苹果推出的用于OC和swift与JS交互的框架,该框架提供了JS运行环境,数据转换以及调用协议等实用且强大的功能。
但是相比WebViewJavaScriptBridge等第三方框架在使用过程中坑还是较多,使用人数较少;如项目中不想使用第三方hybrid交互框架的话,JavaScriptCore也是个不错的选择。
一.简介
首先导入头文件:
#import <JavaScriptCore/JavaScriptCore.h>
点进去可以看到框架的主要成员
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
1.JSVirtualMachine
一个 JSVirtualMachine 实例代表一个执行 JavaScript 的自包含(self-contained)的环境,即虚拟机;它是桥接 JavaScript 和 Objective-C 或 Swift 的基础。
2.JSContext
JSContext为存储JS代码的上下文,在创建的时候会依赖于一个JSVirtualMachine实例:
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
如在初始化时直接init,系统也会初始化一个JSVirtualMachine实例,即虚拟机。一个虚拟机可以包含多个JSContext,在同一个虚拟机下的不同JSContext才可以相互传值,而且JavaScriptCore的API是线程安全的,同一个虚拟机中所有线程只能串行执行,如想实现并发操作就只能通过创建多个虚拟机来实现。
3.JSValue
JSValue为JavaScript 和 Objective-C 或 Swift数据类型转换的桥梁,它提供了多种数据类型的转换方法,数据类型对比如下图:
4.JSManagedValue
将 JSValue 转为 JSManagedValue 类型后,可以添加到 JSVirtualMachine 对象中,这样能够保证你在使用过程中 JSValue 对象不会被释放掉,当你不再需要该 JSValue 对象后,从 JSVirtualMachine 中移除该 JSManagedValue 对象,JSValue 对象就会被释放并置空
5.JSExport
JSExport是一个可以连接JavaScript 和 Objective-C 或 Swift 的强大协议,将实现了该协议的对象传递给JS,那么JS就可以调用该对象的方法,从而实现两者之间的交互。
二.实际运用
1.在网页加载完成之后的初始化工作
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//在网页加载完成之后,初始化JSContext
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//捕获JS异常
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
}
2.调用JS的函数
// 计算两数之和
JSValue *value1 = [self.jsContext evaluateScript:@"add(1,2)"];
// 也可以通过下标的方式获取到方法
JSValue *add = self.jsContext[@"add"];
JSValue *value2 = [add callWithArguments:@[@"1",@"2"]];
3.生成JS函数
self.jsContext[@"add"] = ^() {
//拿到参数数组
NSArray *args = [JSContext currentArguments];
double sum = 0;
for (JSValue *value in args) {
double num = [value toDouble];
sum += num;
}
return sum;
}
4.通过JSExport进行交互
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
//定义一个JSExport protocol
@protocol JSExportTest <JSExport>
JSExportAs(add, - (double)addWithNumberArray:(NSArray *)numberArray ;
@end
//创建一个类遵循JSExport协议
@interface JSBridge : NSObject<JSExportTest>
@end
//实现协议方法
#import "JSBridge.h"
@implementation JSBridge
- (double)addWithNumberArray:(NSArray *)numberArray {
double sum = 0;
for (double number in numberArray) {
sum += number;
}
return sum;
}
//网页加载完毕之后注入交互对象
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//在网页加载完成之后,初始化JSContext
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//捕获JS异常
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//网页加载完毕之后注入交互对象
self.jsContext[@"iOSBridge"] = self.jsBridge;
}
//JS端使用示例:
function test() {
var array=new Array(1,2,3),sum;
sum = iOSBridge.add(array);
alert(sum);
}
//还可以利用runtime为已有类添加协议
@protocol JSUITextFieldExport <JSExport>
@property(nonatomic,copy) NSString *jsText;
@end
- (void)viewDidLoad {
[super viewDidLoad];
class_addProtocol([UITextField class], @protocol(JSUITextFieldExport));
self.jsContext[@"iOSTextField"] = self.textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
textField.jsText = textField.text;
}
//JS端使用示例:
function test() {
alert(iOSTextField.jsText);
}
三.注意事项
1.Block
无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext *)currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。
2.线程问题
线程问题可以看这篇文章:http://www.jianshu.com/p/d616aebf3f14
最后的评论中也有我遇到的问题,线程问题到现在还没有总结分析完整,在此列出只为让各位参考,如有先发现还望告知,不胜感谢!
3.不足
使用JavaScriptCore时,必须首先加载完JS代码,创建JSContext才能进行下一步操作,这就出现了一个很大的不足:JS端在初始化时无法调用OC或Swift代码。在开发中,我采用了等JS加载完成并注入桥接对象之后调用JS函数的方式来完成JS想在初始化时完成的交互操作,但此处需注意线程问题。
四.总结
在最近的一年的工作中陆续用了JavaScriptCore和WebViewJavaScriptBridge,正如开篇所说,在不考虑第三方框架的劣势的情况下,还是WebViewJavaScriptBridge更加便利,因此随后我也将对WebViewJavaScriptBridge的使用和原理进行一次总结。对WebViewJavaScriptBridge的使用和原理感兴趣的朋友可以点击这里