美文网首页
React Native探索系列二——回顾JavaScriptC

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

作者: 西孟___ | 来源:发表于2016-01-20 17:05 被阅读720次

    简述

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

    相关文章

      网友评论

          本文标题:React Native探索系列二——回顾JavaScriptC

          本文链接:https://www.haomeiwen.com/subject/shllkttx.html