• JavaScriptCore


     

    在移动的混合开发中经常用到OC与JS的交互,就涉及iOS中的JavaScriptCore类,下面终结如下

    JavaScriptCore中的类

    在项目中引入JavaScriptCore后,链到头文件中,除了大段的Copyright注释可以看到里面只要引入了5个文件,每个文件里都定义跟文件名对应的类:

    • JSContext
    • JSValue
    • JSManagedValue
    • JSVirtualMachine
    • JSExport

    虽说代码中的注释介绍的也比较详细了,但是如同一图顶万言,对程序员来说代码更有说服力。本文就先来说说这些类相对比较好理解但又很常用的JSContext和JSValue以及它们方法的使用方式和效果。

    JSContext和JSValue

    JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext就为其提供着运行环境,通过- (JSValue *)evaluateScript:(NSString *)script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。而JSContext的创建都是基于JSVirtualMachine- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。

    JSValue则可以说是JavaScript和Object-C之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。其一一对应方式可见下表:

     
     
    Objective-CJavaScriptJSValue ConvertJSValue Constructor
    nil undefined   valueWithUndefinedInContext
    NSNull null   valueWithNullInContext:
    NSString string toString  
    NSNumber number, boolean toNumber
    toBool
    toDouble
    toInt32
    toUInt32
    valueWithBool:inContext:
    valueWithDouble:inContext:
    valueWithInt32:inContext:
    valueWithUInt32:inContext:
    NSDictionary Object object toDictionary valueWithNewObjectInContext:
    NSArray Array object toArray valueWithNewArrayInContext:
    NSDate Date object toDate  
    NSBlock Function object    
    id Wrapper object toObject
    toObjectOfClass:
    valueWithObject:inContext:
    Class Constructor object    

    基本类型转换

    先看个简单的例子:

    复制代码
    1     JSContext *context = [[JSContext alloc] init];
    //iOS调用JS 2 JSValue *jsVal = [context evaluateScript:@"21+7"]; 3 int iVal = [jsVal toInt32]; 4 NSLog(@"JSValue: %@, int: %d", jsVal, iVal); 5 6 //Output: 7 // JSValue: 28, int: 28
    复制代码

    很简单吧,还可以存一个JavaScript变量在JSContext中,然后通过下标来获取出来。而对于Array或者Object类型,JSValue也可以通过下标直接取值和赋值。

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     [context evaluateScript:@"var arr = [21, 7 , 'iderzheng.com'];"];//让JS创建一个数组对象
     3     JSValue *jsArr = context[@"arr"]; // Get array from JSContext
     4      
     5     NSLog(@"JS Array: %@; Length: %@", jsArr, jsArr[@"length"]);
     6     jsArr[1] = @"blog"; // Use JSValue as array
     7     jsArr[7] = @7;
     8      
     9     NSLog(@"JS Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);
    10      
    11     NSArray *nsArr = [jsArr toArray];
    12     NSLog(@"NSArray: %@", nsArr);
    13      
    14     //Output:
    15     // JS Array: 21,7,iderzheng.com Length: 3
    16     // JS Array: 21,blog,iderzheng.com,,,,,7 Length: 8
    17     // NSArray: (
    18     // 21,
    19     // blog,
    20     // "iderzheng.com",
    21     // "<null>",
    22     // "<null>",
    23     // "<null>",
    24     // "<null>",
    25     // 7
    26     // )
    复制代码

    通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且JSValue是遵循JavaScript的数组特性:无下标越位,自动延展数组大小。并且通过JSValue还可以获取JavaScript对象上的属性,比如例子中通过"length"就获取到了JavaScript数组的长度。在转成NSArray的时候,所有的信息也都正确转换了过去。

    方法的转换

    各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。比如在前端开发中常用的log方法,虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     context[@"log"] = ^() {
     3     NSLog(@"+++++++Begin Log+++++++");
     4      
     5     NSArray *args = [JSContext currentArguments];
     6     for (JSValue *jsVal in args) {
     7     NSLog(@"%@", jsVal);
     8     }
     9      
    10     JSValue *this = [JSContext currentThis];
    11     NSLog(@"this: %@",this);
    12     NSLog(@"-------End Log-------");
    13     };
    14      
    15     [context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"];
    16      
    17     //Output:
    18     // +++++++Begin Log+++++++
    19     // ider
    20     // 7,21
    21     // [object Object]
    22     // this: [object GlobalObject]
    23     // -------End Log-------
    复制代码

    通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表(+ (JSContext *)currentContext;)和当前调用该方法的对象(+ (JSValue *)currentThis)。对于"this",输出的内容是GlobalObject,这也是JSContext对象方法- (JSValue *)globalObject;所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。

    Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反过来将参数传进去来调用方法。

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     [context evaluateScript:@"function add(a, b) { return a + b; }"];
    //获取JS中的方法 3 JSValue *add = context[@"add"]; 4 NSLog(@"Func: %@", add); 5
    //将参数传给JS的方法并执行该方法 6 JSValue *sum = [add callWithArguments:@[@(7), @(21)]]; 7 NSLog(@"Sum: %d",[sum toInt32]); 8 //OutPut: 9 // Func: function add(a, b) { return a + b; } 10 // Sum: 28
    复制代码

    JSValue还提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContextglobalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue对象调用。

    异常处理

    Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置exceptionHandler,它接受的是^(JSContext *context, JSValue *exceptionValue)形式的Block。其默认值就是将传入的exceptionValue赋给传入的contextexception属性:

    1     ^(JSContext *context, JSValue *exceptionValue) {
    2         context.exception = exceptionValue;
    3     };

    我们也可以给exceptionHandler赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
     3         NSLog(@"%@", exception);
     4         con.exception = exception;
     5     };
     6      
     7     [context evaluateScript:@"ider.zheng = 21"];
     8      
     9     //Output:
    10     // ReferenceError: Can't find variable: ider
    复制代码

    使用Block的注意事项

    从之前的例子和介绍应该有体会到Block在JavaScriptCore中起到的强大作用,它在JavaScript和Objective-C之间的转换 建立起更多的桥梁,让互通更方便。但是要注意的是无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext *)currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。

    比如上边自定义异常处理方法,就是赋给传入JSContext对象con,而不是其外创建的context对象,虽然它们其实是同一个对象。这是因为Block会对内部使用的在外部定义创建的对象做强引用,而JSContext也会对被赋予的Block做强引用,这样它们之间就形成了循环引用(Circular Reference)使得内存无法正常释放。
    对于JSValue也不能直接从外部引用到Block中,因为每个JSValue上都有JSContext的引用 (@property(readonly, retain) JSContext *context;),JSContext再引用Block同样也会形成引用循环。

     

  • 相关阅读:
    web页面接入QQ客服的方法
    如何使用webapi集成swagger
    TCP的三次握手和四次挥手
    笔记-ASP.NET WebApi
    .net开发人员应该知道的几个网站
    HttpClient在async中产生的代码不执行和堵塞
    【转】CA证书申请+IIS配置HTTPS+默认访问https路径
    WebApi捕捉异常的一套方案
    使用Topshelf部署你的Job
    使用ajax局部更新Razor页面
  • 原文地址:https://www.cnblogs.com/duzhaoquan/p/6015793.html
Copyright © 2020-2023  润新知