JavaScriptCore简介
- JavaScriptCore中主要的类
- JSContext
- JSValue
- JSExport
- JSManagedValue
- JSVirtualMachine
- Native Code 和 JS 之间的互相调用
- Native Code 与UIWebView中的JS交互
- Native Code 与JS文件直接交互
JavaScriptCore背景
iOS中的JavaScriptCore.framework其实只是基于webkit(Safari的浏览器引擎)中 以C/C++实现的JavaScriptCore的一个包装,在iOS7中,Apple将其作为一个标准库供开发者使用
JavaScriptCore主要功能
- JavaScriptCore主要是对JS进行解析和提供执行环境。
- JavaScriptCore可以让我们脱离webview直接运行我们的js
- JavaScriptCore提供一种动态局部升级和更新的逻辑,大大提高应用的可扩展性
- 对手机内嵌web模式的新尝试点,即通过Native+JS file的方式取代webview的方式
JavaScriptCore中主要的类
1.JSContext --- 在OC中创建JavaScript运行的上下文环境
- (instancetype)init; // 创建JSContext对象,获得JavaScript运行的上下文环境
// 在特定的对象空间上创建JSContext对象,获得JavaScript运行的上下文环境
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
// 运行一段js代码,输出结果为JSValue类型
- (JSValue *)evaluateScript:(NSString *)script;
// iOS 8.0以后可以调用此方法
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL NS_AVAILABLE(10_10, 8_0);
// 获取当前正在运行的JavaScript上下文环境
+ (JSContext *)currentContext;
// 返回结果当前执行的js函数 function () { [native code] } ,iOS 8.0以后可以调用此方法
+ (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0);
// 返回结果当前方法的调用者[object Window]
+ (JSValue *)currentThis;
// 返回结果为当前被调用方法的参数
+ (NSArray *)currentArguments;
// js的全局变量 [object Window]
@property (readonly, strong) JSValue *globalObject;
2.JSValue --- JavaScript中的变量和方法,可以转成OC数据类型,每个JSValue都和JSContext相关联并且强引用context
@textblock
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
@/textblock
// 在context创建BOOL的JS变量
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
// 将JS变量转换成OC中的BOOL类型
- (BOOL)toBool;
// 修改JS对象的属性的值
- (void)setValue:(id)value forProperty:(NSString *)property;
// JS中是否有这个对象
@property (readonly) BOOL isUndefined;
// 比较两个JS对象是否相等
- (BOOL)isEqualToObject:(id)value;
// 调用者JSValue对象为JS中的方法名称,arguments为参数,调用JS中Window直接调用的方法
- (JSValue *)callWithArguments:(NSArray *)arguments;
// 调用者JSValue对象为JS中的全局对象名称,method为全局对象的方法名称,arguments为参数
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
// JS中的结构体类型转换为OC
+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context;
- JSExport --- JS调用OC中的方法和属性写在继承自JSExport的协议当中,OC对象实现自定义的协议
// textFunction -- JS方法
// - (void) ocTestFunction:(NSNumber *)value sec:(NSNumber *)number -- OC方法
JSExportAs (textFunction,- (void) ocTestFunction:(NSNumber *)value sec:(NSNumber *)number);
4.JSManagedValue --- JS和OC对象的内存管理辅助对象,主要用来保存JSValue对象,解决OC对象中存储js的值,导致的循环引用问题
JSManagedValue *_jsManagedValue = [JSManagedValue managedValueWithValue:jsValue];
[_context.virtualMachine addManagedReference:_jsManagedValue];
- JSManagedValue本身只弱引用js值,需要调用JSVirtualMachine的addManagedReference:withOwner:把它添加到JSVirtualMachine中,这样如果JavaScript能够找到该JSValue的Objective-C owner,该JSValue的引用就不会被释放。
5.JSVirtualMachine --- JS运行的虚拟机,有独立的堆空间和垃圾回收机制,运行在不同虚拟机环境的JSContext可以通过此类通信
Native Code 和 JS 之间的互相调用(以UIWebView中的JS为例)
OC调用JS代码
js代码
function showAlert(text)
{
alert(text);
}
1 .通过webView直接调用
[webView stringByEvaluatingJavaScriptFromString:@"showAlert('hahaha')"];
2.通过jsvalue
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSValue *inputValue = context[@"showAlert"];
[inputValue callWithArguments:@[@"hahaha"]];
}
JS调用OC代码
<button onclick="showAlert('haha')">showAlert</button>
1.通过JSContext
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//JS调用OC的回调方法,是在子线程,所以需要更新OC中的UI的话,需要切换到主线程
context[@"showAlert"] = ^(NSString *str){
NSLog(@"%@ %@",str,[NSThread currentThread]);
};
}
2.通过JSExport
<button onclick="native.myLog('123456');">调用OC中myLog方法</button>
自定义协议遵循JSExport协议 通过JSExportAS宏把mylog(JS的方法名)和myOCLog(OC发放)关联起来
@protocol WebExport <JSExport>
JSExportAs(myLog ,- (void)myOCLog :(NSString *)string);
@end
@interface UIWebViewViewController () <UIWebViewDelegate,WebExport>
@end
指定context的native 为self(遵守了上面协议的对象)
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"native"] = self;
}
//JS调用OC的回调方法,是在子线程,所以需要更新OC中的UI的话,需要切换到主线程
- (void)myOCLog :(NSString *)string
{
NSLog(@"%@",string);
NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
然后 js代码: native.myLog('123456')
就相当于调用OC代码: [self myOCLog:@"123456"];
这里需要主要 self强引用了context对象 而context[@"native"]也强引用了self 这里造成了循环引用。一般的解决方法是 把这里的self替换成一个中间对象来处理
JavaScriptCore和UIWebView的使用的注意事项
1.OC在与UIWebView中的JS交互的逻辑是,先获取UIWebView中的JS的执行环境
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
2.获取UIWebView中的JS的执行环境的时机,一般在webViewDidFinishLoad时获取,获取不到的情况下,需改在其他方法中获取
shouldStartLoadWithRequest:Sent before a web view begins loading a frame
webViewDidStartLoad:Sent after a web view starts loading a frame.
webViewDidFinishLoad:Sent after a web view finishes loading a frame
3.线程问题
1.JavaScriptCore中提供的API都是线程安全的,一个JSVirtualMachine在一个线程中,它可以包含多个JSContext,而且相互之间可以传值,为了确保线程安全,这些context在运行的时候会采用锁,可以认为是串行执行
2.JS调用OC的回调方法,是在子线程,所以需要更新OC中的UI的话,需要切换到主线程
4.内存问题
oc中使用ARC方式管理内存(基于引用计数),但JavaScriptCore中使用的是垃圾回收方式,其中所有的引用都是强引用,但是我们不必担心其循环引用,js的垃圾回收能够打破这些强引用,有些情况需要考虑如下
- js调起OC回调的block中获取JSConetxt容易循环引用
self.jsContext[@"jsCallNative"] = ^(NSString *paramer){
// 会引起循环引用
JSValue *value1 = [JSValue valueWithNewObjectInContext:
self.jsContext];
// 不会引起循环引用
JSValue *value = [JSValue valueWithNewObjectInContext:
[JSContext currentContext]];
};
JavaScriptCore中所有的引用都是强引用,所以在OC中需要存储JS中的值的时候,需要注意
- 在oc中为了打破循环引用我们采用weak的方式,不过在JavaScriptCore中我们采用内存管理辅助对象JSManagedValue的方式,它能帮助引用计数和垃圾回收这两种内存管理机制之间进行正确的转换
JavaScriptCore单独使用
js代码如下:
globalObject = new Object();
globalObject.name = 100;
globalObject.nativeCallJS = function (parameter) {
alert (parameter);
};
OC读取JS文件,并相互通信
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"js"];
NSString *jsContent = [[NSString alloc] initWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
JSContext *jsContext = [[JSContext alloc] init];
//捕获运行js脚本的错误信息
jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
};
//js脚本添加到当前的js执行环境中
[jsContext evaluateScript:jsContent];
self.jsManager = [[JSManager alloc] init];
jsContext[@"globalObject"] = self.jsManager;
...
...