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的引用并在合適時機釋放的機制岩臣。