本博客主要分以下几个方面来介绍iOS中的JavaScriptCore
- JavaScriptCore简介
- JavaScriptCore中主要的类
- JSContext
- JSValue
- JSExport
- JSManagedValue
- JSVirtualMachine
- Native Code 和 JS 之间的互相调用
- Native Code 与UIWebView中的JS交互
- Native Code 与JS文件直接交互
JavaScriptCore简介
JavaScriptCore背景
- iOS中的JavaScriptCore.framework其实只是基于webkit(Safari的浏览器引擎)中以C/C++实现的JavaScriptCore的一个包装,在iOS7中,Apple将其作为一个标准库供开发者使用
JavaScriptCore主要功能
- JavaScriptCore主要是对JS进行解析和提供执行环境。代码是开源的,JavaScriptCore源码
- JavaScriptCore可以让我们脱离webview直接运行我们的js
- JavaScriptCore提供一种动态局部升级和更新的逻辑,大大提高应用的可扩展性
- 对手机内嵌web模式的新尝试点,即通过Native+JS file的方式取代webview的方式
JavaScriptCore中主要的类
-
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;
-
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);
-
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的引用就不会被释放。
JSVirtualMachine --- JS运行的虚拟机,有独立的堆空间和垃圾回收机制,运行在不同虚拟机环境的JSContext可以通过此类通信。
Native Code 和 JS 之间的互相调用(以UIWebView中的JS为例)
- JS中,点击事件直接调用方法方式JS和Native代码的互调如下:
-
1.1 JS代码如下
<html> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport"> <body> <script type="text/javascript"> var nativeCallJS = function(parameter) { alert (parameter); }; </script> <button type="button" onclick = "jsCallNative('jsParameter')" style="width:100%; height:30px;"/>调用OC代码</button> </body> </html>
-
1.2 OC代码如下
- (void)__jsLogic { self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception){ NSLog(@"JS代码执行中的异常信息%@", exception); }; self.jsContext = [self valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; self.jsContext[@"jsCallNative"] = ^(NSString *paramer){ JSValue *currentThis = [JSContext currentThis]; JSValue *currentCallee = [JSContext currentCallee]; NSArray *currentParamers = [JSContext currentArguments]; dispatch_async(dispatch_get_main_queue(), ^{ /** * js调起OC代码,代码在子线程,更新OC中的UI,需要回到主线程 */ }); NSLog(@"JS paramer is %@",paramer); NSLog(@"currentThis is %@",[currentThis toString]); NSLog(@"currentCallee is %@",[currentCallee toString]); NSLog(@"currentParamers is %@",currentParamers); }; JSValue *jsMethod = self.jsContext[@"nativeCallJS"]; [jsMethod callWithArguments:@[@"nativeCallJS"]]; }
-
1.3 OC运行结果,弹出HTML中的alert提示
2016-08-05 17:57:08.974 LeWebViewPro[38150:3082770] JS paramer is jsParameter 2016-08-05 17:57:08.975 LeWebViewPro[38150:3082770] currentThis is [object Window] 2016-08-05 17:57:08.975 LeWebViewPro[38150:3082770] currentCallee is function () { [native code] } 2016-08-05 17:57:08.975 LeWebViewPro[38150:3082770] currentParamers is ( jsParameter )
- JS中,点击事件等事件通过调用全局对象的方法调用方法,JS和Native代码的互调如下:
-
2.1 JS代码如下
<html> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport"> <body> <script type="text/javascript"> globalObject = new Object(); globalObject.name = 100; globalObject.nativeCallJS = function (parameter) { alert (parameter); }; </script> <button type="button" onclick = "globalObject.jsCallNative('jsParameter')" style="width:100%; height:30px;"/>调用OC代码</button> </body> </html>
2.2 OC代码如下
-
2.2.1 JSManager 代码,负责执行JS中的方法
#import <JavaScriptCore/JavaScriptCore.h> #import <Foundation/Foundation.h> @protocol LeJSExport <JSExport> JSExportAs (jsCallNative,- (void) jsCallNative:(NSString *)jsParameter); @end @interface JSManager : NSObject<LeJSExport> @end -----.M文件----- #import "JSManager.h" @implementation JSManager - (void)jsCallNative:(NSString *)jsParameter { JSValue *currentThis = [JSContext currentThis]; JSValue *currentCallee = [JSContext currentCallee]; NSArray *currentParamers = [JSContext currentArguments]; dispatch_async(dispatch_get_main_queue(), ^{ /** * js调起OC代码,代码在子线程,更新OC中的UI,需要回到主线程 */ }); NSLog(@"JS paramer is %@",jsParameter); NSLog(@"currentThis is %@",[currentThis toString]); NSLog(@"currentCallee is %@",[currentCallee toString]); NSLog(@"currentParamers is %@",currentParamers); } @end
-
2.2.2 包含UIWebView类的代码,负责调起JS中的方法
- (void)nativeCallJS { self.jsManager = [[JSManager alloc] init]; self.jsContext = [self valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception){ NSLog(@"JS代码执行中的异常信息%@", exception); }; self.jsContext[@"globalObject"] = self.jsManager; //1.OC方法调起JS JSValue *varibleStyle = self.jsContext[@"globalObject"]; [varibleStyle invokeMethod:@"nativeCallJS" withArguments:@[@100]]; //2.OC脚本调起JS NSString *jsScript = [NSString stringWithFormat:@"globalObject.nativeCallJS('%@')",@100]; [self.jsContext evaluateScript:jsScript]; }
-
2.3 OC运行结果,弹出HTML中的alert提示
2016-08-07 10:30:11.444 LeWebViewPro[46674:3253852] JS paramer is jsParameter 2016-08-07 10:30:11.444 LeWebViewPro[46674:3253852] currentThis is [object JSManager] 2016-08-07 10:30:11.444 LeWebViewPro[46674:3253852] currentCallee is function () { [native code] } 2016-08-07 10:30:11.445 LeWebViewPro[46674:3253852] currentParamers is ( jsParameter )
-
JavaScriptCore和UIWebView的使用的注意事项
-
OC在与UIWebView中的JS交互的逻辑是,先获取UIWebView中的JS的执行环境
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
-
获取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
-
线程问题
- JavaScriptCore中提供的API都是线程安全的,一个JSVirtualMachine在一个线程中,它可以包含多个JSContext,而且相互之间可以传值,为了确保线程安全,这些context在运行的时候会采用锁,可以认为是串行执行。
- JS调用OC的回调方法,是在子线程,所以需要更新OC中的UI的话,需要切换到主线程
-
内存问题
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代码如下test.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;
...
...
- jS与OC的交互与通过UIWebView相同