React Native探索系列二——回顾JavaScriptCore

简述

说到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 
@property (nonatomic, copy) NSString *name;
@property (nonatomic )      NSInteger   number;
@end

@interface Thing : NSObject 
@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的文章,有些内容做了修改,也加了一些自己的理解,测试结果全部由自主实现,仅供大家参考。

你可能感兴趣的:(React Native探索系列二——回顾JavaScriptCore)