简述
说到react native实现的js-oc之间的互相通信,有的同学很快就想到了javascriptcore,但是这个javascriptcore框架是iOS7才推出的,因此react native只是用了iOS自带的javascriptcore作为js的解析引擎,但没有用到javascriptcore框架提供的js与oc互调的特性,而是自己实现了一套机制,这套机制可以通用于所有JS引擎上,在没有JavaScriptCore的情况下用webview作为解析引擎,用于兼容iOS7以下没有JavascriptCore的版本。
iOS7之前,iOS app与javascript的交互只有一种方式,那就是UIWebView暴露的stringByEvaluatingJavaScriptFromString:方法,你可以使用这个简单的api在web视图上显示html文档。iOS7以后,开发者可以深入了解javascript运行时,可以访问变量、接收回调、共享oc对象,这样就有了oc-js交互的可能。
oc-js通信的简单实现
简单变量值修改
javascript运行在一个JSVirtualMachine类呈现的虚拟机中,JSVirtualMachine是轻量级的,但重要的一点,可以实例化多个JSVirtualMachine来支持多线程的javascript,每个JSVirtualMachine可以是任意数量的JSContexts,一个JSContext对应一个JavaScript运行时环境,并提供了一些关键功能,两个是特别重要的快速访问:访问全局对象,执行脚本的能力。
JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
context[@"a"] = @5;
JSValue *aValue = context[@"a"];
double a = [aValue toDouble];
NSLog(@"%.0f", a);
控制台信息:
2016-01-20 14:50:56.897 XWTestDemo[3927:1459293] 5
接着执行下面的代码
[context evaluateScript:@"a=10"];
JSValue *newValue = context[@"a"];
NSLog(@"%.0f", [newValue toDouble]);
控制台信息:
2016-01-20 14:56:19.590 XWTestDemo[4011:1489852] 10
执行功能需求
想要用javascript执行环境做些有用的事情,就要能执行一些javascript 代码,跟UIWebview不同,JSContext像是一张白纸,需要创建函数并执行它。
[context evaluateScript:@"var square = function(x) {return x*x;}"];
JSValue *squareFunction = context[@"square"];
NSLog(@"%@", squareFunction);
JSValue *aSquared = [squareFunction callWithArguments:@[context[@"a"]]];
NSLog(@"a^2: %@", aSquared);
JSValue *nineSquared = [squareFunction callWithArguments:@[@9]];
NSLog(@"9^2: %@", nineSquared);
控制台信息:
2016-01-20 15:06:37.272 XWTestDemo[4171:1552767] function (x) {return x*x;}
2016-01-20 15:06:37.272 XWTestDemo[4171:1552767] a^2: 100
2016-01-20 15:06:37.273 XWTestDemo[4171:1552767] 9^2: 81
JSValue的callWithArguments方法会取出数组中的参数,但是要确保接收者是一个有效的javascript方法,否则将会失效,比如我们修改下square方法,会得到下面的输出信息
2016-01-20 15:24:05.194 XWTestDemo[4418:1646313] a^2: undefined
2016-01-20 15:24:05.195 XWTestDemo[4418:1646313] 9^2: undefined
除了普通赋值外,还可以将一个oc的block代码块赋给JSContext,如下:
context[@"factorial"] = ^(int x) {
int factorial = 1;
for (; x > 1; x--) {
factorial *= x;
}
return factorial;
};
[context evaluateScript:@"var fiveFactorial = factorial(5);"];
JSValue *fiveFactorial = context[@"fiveFactorial"];
NSLog(@"5! = %@", fiveFactorial);
控制台信息:
2016-01-20 15:35:11.325 XWTestDemo[4668:1705564] 5! = 120
通过这种方式,可以执行oc里的回调处理,同时有些注意点,应该避免从block中获取JSValue或者JSContext,因为这些对象的循环引用可能会导致泄露。
对象数据同步
JSValue包装了各种JavaScript的值,包括基本数据类型和对象,如下表:
Objective-C type | JavaScript type
-----------------------------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock | Function object
id | Wrapper object
Class | Constructor object
从表中可以看到两点,一时没有可变类型,二是传递给JSContext对象时,如果对象类型不是NSNull NSString,NSNumber NSDictionary,NSArray、NSDate或NSBlock,JavaScriptCore将相关的类层次结构导入到JavaScript执行上下文并创建出等价类和原型。我们可以使用JSExport协议暴露部分定制类,JavaScript将会创建一个包装对象另透传。因此一个对象可以共享读取或改变。
示例:
@protocol ThingJSExports <JSExport>
@property (nonatomic, copy) NSString *name;
@property (nonatomic ) NSInteger number;
@end
@interface Thing : NSObject <ThingJSExports>
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSInteger number;
@end
@implementation Thing
- (NSString *)description {
return [NSString stringWithFormat:@"%@: %d", self.name, self.number];
}
@end
执行下面的代码
Thing *thing = [[Thing alloc] init];
thing.name = @"Joan";
thing.number = 5;
context[@"thing"] = thing;
JSValue *thingValue = context[@"thing"];
NSLog(@"Thing: %@", thing);
NSLog(@"Thing JSValue: %@", thingValue);
thing.name = @"Betty";
thing.number = 8;
NSLog(@"Thing: %@", thing);
NSLog(@"Thing JSValue: %@", thingValue);
JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
[context evaluateScript:@"thing.name = \"Carlos\"; thing.number = 5"];
NSLog(@"Thing: %@", thing);
NSLog(@"Thing JSValue: %@", thingValue);
控制台打印信息如下:
2016-01-20 16:53:24.882 XWTestDemo[5946:2182910] Thing: Joan:5
2016-01-20 16:53:24.883 XWTestDemo[5946:2182910] Thing JSValue: Joan:5
2016-01-20 16:53:37.476 XWTestDemo[5946:2182910] Thing: Betty:8
2016-01-20 16:53:37.477 XWTestDemo[5946:2182910] Thing JSValue: Betty:8
2016-01-20 16:53:39.130 XWTestDemo[5946:2182910] Thing: Carlos:5
2016-01-20 16:53:39.130 XWTestDemo[5946:2182910] Thing JSValue: Carlos:5
当然你也可以只暴露一个属性,比如注销到下面这个,如:
//@property (nonatomic, copy) NSString *name;
我们再看下运行结果:
2016-01-20 16:55:39.017 XWTestDemo[6002:2194733] Thing: Joan:5
2016-01-20 16:55:39.017 XWTestDemo[6002:2194733] Thing JSValue: Joan:5
2016-01-20 16:55:57.714 XWTestDemo[6002:2194733] Thing: Betty:8
2016-01-20 16:55:57.714 XWTestDemo[6002:2194733] Thing JSValue: Betty:8
2016-01-20 16:55:59.941 XWTestDemo[6002:2194733] Thing: Betty:5
2016-01-20 16:55:59.941 XWTestDemo[6002:2194733] Thing JSValue: Betty:5
可以看出当想通过evaluateScript方法改变thing对象的name属性的值时,由于这个属性没有暴露出来,导致修改不成功。
这篇文章只能帮助你了解javascriptcore的皮毛,想要了解更多可能阅读下JavaScriptCore的API源码.
声明:本文主要翻译了Owen Mathews的文章,有些内容做了修改,也加了一些自己的理解,测试结果全部由自主实现,仅供大家参考。