iOS-JavaScriptCore

JavaScriptCore是Safari的JavaScript引擎弧圆,在iOS7之后蘋果開放了JavaScriptCore框架软瞎,開發(fā)者可以通過其提供的OC接口來使用JavaScriptCore庵朝。說白了就是它提供了執(zhí)行JavaScript代碼的能力蔓挖,相當(dāng)于一個JavaScript的虛擬機齐鲤。JavaScriptCore是開源的,感興趣可以研究一下:https://trac.webkit.org/browser/trunk/Source/JavaScriptCore

JavaScriptCore.h中包含了框架中幾個比較重要的類:

#import "JSContext.h"         //js上下文筒愚,執(zhí)行js代碼
#import "JSValue.h"           //封裝js數(shù)據(jù)類型   
#import "JSManagedValue.h"    //管理JSValue內(nèi)存
#import "JSVirtualMachine.h"  //JavaScript虛擬機,js底層執(zhí)行環(huán)境
#import "JSExport.h"          //導(dǎo)出OC對象

下面結(jié)合幾個簡單的例子菩浙,理解這些對象所扮演的角色:

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執(zhí)行環(huán)境巢掺,實現(xiàn)并發(fā)執(zhí)行和內(nèi)存管理(GC)句伶。而JSContext處理具體的JavaScript代碼,每個JSContext都屬于一個JSVirtualMachine陆淀,相同JSVirtualMachine內(nèi)的JSContext之間可以互相傳值考余,不同虛擬機之間則不能傳值,因為它們有自己獨立的堆空間和垃圾回收器倔约。
  • JSValue代表一個JavaScript值秃殉,提供一些基本數(shù)據(jù)類型在js和native之間的轉(zhuǎn)換。每個JSValue會強引用它所在的JSContext浸剩,所以只要有一個JSValue被持有钾军,它的JSContext就會一直存在。而JSManagedValue是可以自動管理內(nèi)存的Value對象绢要,這點會在后面“內(nèi)存管理”部分詳細(xì)討論吏恭。


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對應(yīng)NSDictionary
    JSValue *b = ctx[@"b"];
    NSLog(@"%@",[b toDictionary]);
    // array對應(yīng)NSArray
    JSValue *c = ctx[@"c"];
    NSLog(@"%@",[c toArray]);
}
  • oc訪問js對象和對它的賦值都是通過key-value的方式,具體有三種寫法:
    1.[]方式重罪,直接context[key]獲扔:摺;
    2.通過context的objectForKeyedSubscript方法
    3.context.globalObject可以獲取js全局對象
  • jsObject對應(yīng)NSDictionary剿配,jsArray對應(yīng)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可以轉(zhuǎn)換成為js中的function對象
  • js function無法直接轉(zhuǎn)成native的block搅幅,js function也是一個對象,而對于block參數(shù)個數(shù)呼胚、類型還有返回類型都是固定的茄唐。可以通過下面的方法來執(zhí)行:
//JSValue.h
//JSValue本身是一個function蝇更,通過callWithArguments調(diào)用
- (JSValue *)callWithArguments:(NSArray *)arguments;
//以構(gòu)造方法執(zhí)行
- (JSValue *)constructWithArguments:(NSArray *)arguments;
//執(zhí)行該JSValue對象的某個方法
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;

4.JSExport

JSExport提供一種聲明式的方法將OC的類和其屬性方法導(dǎo)出到JavaScript:
首先要定義一個協(xié)議繼承自JSExport沪编,在其中聲明需要導(dǎo)出的屬性和方法,實例類實現(xiàn)該協(xié)議并提供相關(guān)實現(xiàn)年扩。

//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導(dǎo)出到j(luò)s蚁廓,提供了初始化方法和-show方法,我們用一段js代碼創(chuàng)建這么一個view放在屏幕上:

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

這里有個細(xì)節(jié)厨幻,寫過JSPatch應(yīng)該都知道相嵌,像CGRect這種結(jié)構(gòu)體,在js中應(yīng)該全部展開來寫况脆,比如{x:0,y:0,width:200,height:300}饭宾,而不能寫成{origin:{x:0, y:0}, size:{width:200, height:300}}

執(zhí)行的代碼如下:

//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.內(nèi)存管理

oc的內(nèi)存管理是引用計數(shù),而JavaScript則是垃圾回收機制漠另。之前有提到每個JSValue會強引用它的JSContext:

//JSValue.h
@property (readonly, strong) JSContext *context;
  • 如果我們導(dǎo)出一個native對象到j(luò)s捏雌,則它會被全局對象globalObject持有,如果在這個對象中強引用了JSContext或者JSValue(value持有context)笆搓,就會造成循環(huán)引用性湿。
    尤其在Block中纬傲,直接使用外部的JSContext或JSValue就會造成循環(huán)引用,可以通過JSContext的類方法+ (JSContext *)currentContext來獲得肤频,或者當(dāng)做參數(shù)傳入叹括。

  • 必要時使用JSManagedValue。JSManagedValue提供了自動管理內(nèi)存的特性宵荒,相當(dāng)于JSManagedValue引用了真正的JSValue汁雷,并可以通過+ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:(id)owner; 添加引用關(guān)系,當(dāng)沒有native引用關(guān)系报咳,且在js環(huán)境中也被回收時侠讯,會釋放JSValue并置為nil(官方JSManagedValue注釋)。
    簡單說就是我們常用的weak引用暑刃,不能直接用weak是因為JSValue對應(yīng)的js對象是由垃圾回收器(GC)管理的厢漩,如果使用weak可能在我們需要它的時候已經(jīng)被回收。所以通過JSManagedValue實現(xiàn)了對JSValue的引用并在合適時機釋放的機制岩臣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末溜嗜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子架谎,更是在濱河造成了極大的恐慌炸宵,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谷扣,死亡現(xiàn)場離奇詭異土全,居然都是意外死亡,警方通過查閱死者的電腦和手機抑钟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門涯曲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來野哭,“玉大人在塔,你說我怎么就攤上這事〔η” “怎么了蛔溃?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長篱蝇。 經(jīng)常有香客問我贺待,道長,這世上最難降的妖魔是什么零截? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任麸塞,我火速辦了婚禮,結(jié)果婚禮上涧衙,老公的妹妹穿的比我還像新娘哪工。我一直安慰自己奥此,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布雁比。 她就那樣靜靜地躺著稚虎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪偎捎。 梳的紋絲不亂的頭發(fā)上蠢终,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音茴她,去河邊找鬼寻拂。 笑死,一個胖子當(dāng)著我的面吹牛丈牢,可吹牛的內(nèi)容都是我干的兜喻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼赡麦,長吁一口氣:“原來是場噩夢啊……” “哼朴皆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泛粹,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤遂铡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晶姊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扒接,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年们衙,在試婚紗的時候發(fā)現(xiàn)自己被綠了钾怔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒙挑,死狀恐怖宗侦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忆蚀,我是刑警寧澤矾利,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站馋袜,受9級特大地震影響男旗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜欣鳖,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一察皇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泽台,春花似錦什荣、人聲如沸呀忧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽而账。三九已至,卻和暖如春因篇,著一層夾襖步出監(jiān)牢的瞬間泞辐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工竞滓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咐吼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓商佑,卻偏偏與公主長得像锯茄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茶没,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容