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之间可以互相传值,不同虚拟机之间则不能传值,因为它们有自己独立的堆空间和垃圾回收器。
  • 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 <JavaScriptCore/JavaScriptCore.h>

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

@interface MyView : UIView <MyViewExports>

@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的引用并在合适时机释放的机制。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容