iOS-JavaScriptCore

JavaScriptCore是Safari的JavaScript引擎,在iOS7之后苹果开放了JavaScriptCore框架,开发者可以通过其提供的OC接口来使用JavaScriptCore。说白了就是它提供了执行JavaScript代码的能力,相当于一个JavaScript的虚拟机。JavaScriptCore是开源的,感兴趣可以研究一下:https://trac.webkit.org/browser/trunk/Source/JavaScriptCore

JavaScriptCore.h中包含了框架中几个比较重要的类:

#import "JSContext.h"         //js上下文,执行js代码
#import "JSValue.h"           //封装js数据类型   
#import "JSManagedValue.h"    //管理JSValue内存
#import "JSVirtualMachine.h"  //JavaScript虚拟机,js底层执行环境
#import "JSExport.h"          //导出OC对象

下面结合几个简单的例子,理解这些对象所扮演的角色:

1.JSContext和JSValue
//demo1.js
1+2*3
//objective-c
- (void)test1
{
    JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
    
    JSContext *ctx = [[JSContext alloc] initWithVirtualMachine:vm];
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"demo1" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    
    JSValue *value = [ctx evaluateScript:script];
    
    NSLog(@"%d",[value toInt32]);  //output:7
}
  • 一个JSVirtualMachine是一个完整独立的JavaScript执行环境,实现并发执行和内存管理(GC)。而JSContext处理具体的JavaScript代码,每个JSContext都属于一个JSVirtualMachine,相同JSVirtualMachine内的JSContext之间可以互相传值,不同虚拟机之间则不能传值,因为它们有自己独立的堆空间和垃圾回收器。
    iOS-JavaScriptCore_第1张图片
  • JSValue代表一个JavaScript值,提供一些基本数据类型在js和native之间的转换。每个JSValue会强引用它所在的JSContext,所以只要有一个JSValue被持有,它的JSContext就会一直存在。而JSManagedValue是可以自动管理内存的Value对象,这点会在后面“内存管理”部分详细讨论。


2.访问js对象
//demo2.js
var a = 1+2+3+4+5;
var b = {
    'red':255,
    'blue':0,
    'green':255
};

var c = [b];
//objective-c
- (void)test2
{
    JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
    
    JSContext *ctx = [[JSContext alloc] initWithVirtualMachine:vm];
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"demo2" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    
    [ctx evaluateScript:script];
    
    // 访问js对象的三种方式
    NSLog(@"[] %@",ctx[@"a"]);
    NSLog(@"objectForKeyedSubscript %@",[ctx objectForKeyedSubscript:@"a"]);
    NSLog(@"globalObject %@",[ctx.globalObject objectForKeyedSubscript:@"a"]);
    
    // 同样也可以赋值
    ctx[@"a"] = [JSValue valueWithInt32:90 inContext:ctx];
    [ctx setObject:[JSValue valueWithInt32:90 inContext:ctx] forKeyedSubscript:@"a"];
    [ctx.globalObject setObject:[JSValue valueWithInt32:90 inContext:ctx] forKeyedSubscript:@"a"];
    NSLog(@"[] %@",ctx[@"a"]);
    NSLog(@"objectForKeyedSubscript %@",[ctx objectForKeyedSubscript:@"a"]);
    NSLog(@"globalObject %@",[ctx.globalObject objectForKeyedSubscript:@"a"]);
    
    // object对应NSDictionary
    JSValue *b = ctx[@"b"];
    NSLog(@"%@",[b toDictionary]);
    // array对应NSArray
    JSValue *c = ctx[@"c"];
    NSLog(@"%@",[c toArray]);
}
  • oc访问js对象和对它的赋值都是通过key-value的方式,具体有三种写法:
    1.[]方式,直接context[key]获取;
    2.通过context的objectForKeyedSubscript方法
    3.context.globalObject可以获取js全局对象
  • jsObject对应NSDictionary,jsArray对应NSArray

3.block与js function
//demo3.js
var sum = 0;
//由native注入add和myLog方法
for (let i=0;i<=100;i++){ 
    sum = add(sum,i);
}

myLog("sum is "+sum);  //output: sum is 5050
//objective-c
- (void)test3
{
    JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
    
    JSContext *ctx = [[JSContext alloc] initWithVirtualMachine:vm];
    
    ctx[@"myLog"] = ^(NSString *s){
        NSLog(@"myLog:%@",s);
    };
    
    ctx[@"add"] = ^(NSInteger a, NSInteger b){
        return a+b;
    };
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"demo3" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [ctx evaluateScript:script];
}
  • native的block可以转换成为js中的function对象
  • js function无法直接转成native的block,js function也是一个对象,而对于block参数个数、类型还有返回类型都是固定的。可以通过下面的方法来执行:
//JSValue.h
//JSValue本身是一个function,通过callWithArguments调用
- (JSValue *)callWithArguments:(NSArray *)arguments;
//以构造方法执行
- (JSValue *)constructWithArguments:(NSArray *)arguments;
//执行该JSValue对象的某个方法
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;

4.JSExport

JSExport提供一种声明式的方法将OC的类和其属性方法导出到JavaScript:
首先要定义一个协议继承自JSExport,在其中声明需要导出的属性和方法,实例类实现该协议并提供相关实现。

//objective-c
//MyView.h
#include 

@protocol MyViewExports 
- (instancetype)initWithFrame:(CGRect)frame;
- (void)show;
@end

@interface MyView : UIView 

@property(class,nonatomic,weak) UIViewController    *vc;

@end
//objective-c
//MyView.m
#import "MyView.h"

@implementation MyView
static id _vc = nil;
- (instancetype)initWithFrame:(CGRect)frame;
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = UIColor.redColor;
    }
    return self;
}

- (void)show
{
    [self.class.vc.view addSubview:self];
}

+(void)setVc:(UIViewController *)vc
{
    _vc = vc;
}
+(UIViewController *)vc
{
    return (UIViewController *)_vc;
}

@end

上面代码将一个native自定义的view导出到js,提供了初始化方法和-show方法,我们用一段js代码创建这么一个view放在屏幕上:

//demo4.js
var view = new MyView({x:0,y:0,width:200,height:300});
view.show();

这里有个细节,写过JSPatch应该都知道,像CGRect这种结构体,在js中应该全部展开来写,比如{x:0,y:0,width:200,height:300},而不能写成{origin:{x:0, y:0}, size:{width:200, height:300}}

执行的代码如下:

//objective-c
- (void)test4
{
    MyView.vc = self;
    JSContext *ctx = [[JSContext alloc] init];
    ctx[@"MyView"] = [MyView class];
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"demo4" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [ctx evaluateScript:script];
}

5.内存管理

oc的内存管理是引用计数,而JavaScript则是垃圾回收机制。之前有提到每个JSValue会强引用它的JSContext:

//JSValue.h
@property (readonly, strong) JSContext *context;
  • 如果我们导出一个native对象到js,则它会被全局对象globalObject持有,如果在这个对象中强引用了JSContext或者JSValue(value持有context),就会造成循环引用。
    尤其在Block中,直接使用外部的JSContext或JSValue就会造成循环引用,可以通过JSContext的类方法+ (JSContext *)currentContext来获得,或者当做参数传入。

  • 必要时使用JSManagedValue。JSManagedValue提供了自动管理内存的特性,相当于JSManagedValue引用了真正的JSValue,并可以通过+ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:(id)owner; 添加引用关系,当没有native引用关系,且在js环境中也被回收时,会释放JSValue并置为nil(官方JSManagedValue注释)。
    简单说就是我们常用的weak引用,不能直接用weak是因为JSValue对应的js对象是由垃圾回收器(GC)管理的,如果使用weak可能在我们需要它的时候已经被回收。所以通过JSManagedValue实现了对JSValue的引用并在合适时机释放的机制。

你可能感兴趣的:(iOS-JavaScriptCore)