前阵子,Apple正式发布了新的iOS 7系统,最大最直观的改变在于界面变得小清新范了,我也提到。不过对于移动开发者来说,除了要适应Xcode 5,最应该关注的还是iOS 7在开发接口的上的变化。概览Apple提供的官方文档 ,最最让我欣喜的是iOS 7中加入了JavaScriptCore框架。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。

iOS7新JavaScriptCore框架入门介绍_ide

这个框架其实只是基​​t​​​中以C/C++实现的JavaScriptCore的一个包装,在旧版本iOS开发中,很多开发者也会自行将webkit的库引入项目编译使用。不过虽然iOS7把它当成了标准库,可惜目前,我还没有在Apple Developer中找到像​​​​那样的官方文档介绍这个框架的具体使用方法。

好在还可以在Xcode中找到头文件,而且里面的注释对每个类和方法的功能写得还算清楚,再结合网上仅有的几篇文章的介绍,我也在此简单入门一下JavaScriptCore框架在基本数据类型上的使用。在另一篇》中,我更深入地介绍该框架在对象上的使用。

JavaScriptCore中的类

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

  • JSContext
  • JSValue
  • JSManagedValue
  • JSVirtualMachine
  • JSExport

iOS7新JavaScriptCore框架入门介绍_lua_02

虽说代码中的注释介绍的也比较详细了,但是如同一图顶万言,对程序员来说代码更有说服力。本文就先来说说这些类相对比较好理解但又很常用的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-C

JavaScript

JSValue Convert

JSValue 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

 

 

 

基本类型转换

先看个简单的例子:



iOS7新JavaScriptCore框架入门介绍_ide_03

1     JSContext *context = [[JSContext alloc] init];
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


iOS7新JavaScriptCore框架入门介绍_ide_03


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



iOS7新JavaScriptCore框架入门介绍_ide_03

1     JSContext *context = [[JSContext alloc] init];
2 [context evaluateScript:@"var arr = [21, 7 , 'iderzheng.com'];"];
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 // )


iOS7新JavaScriptCore框架入门介绍_ide_03


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

方法的转换

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



iOS7新JavaScriptCore框架入门介绍_ide_03

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-------


iOS7新JavaScriptCore框架入门介绍_ide_03


通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,​​JSContext​​提供了类方法来获取参数列表(​​+ (JSContext *)currentContext;​​)和当前调用该方法的对象(​​+ (JSValue *)currentThis​ ​)。对于​​"this"​​,输出的内容是​​GlobalObject​​,这也是​​JSContext​​对象方法​​- (JSValue *)globalObject;​ ​所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是 Block可以传入​​JSContext​​作方法,但是​​JSValue​​没有​​toBlock​​方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是​​JSValue​​提供了​​- (JSValue *)callWithArguments:(NSArray *)arguments;​ ​方法可以反过来将参数传进去来调用方法。



iOS7新JavaScriptCore框架入门介绍_ide_03

1     JSContext *context = [[JSContext alloc] init];
2 [context evaluateScript:@"function add(a, b) { return a + b; }"];
3 JSValue *add = context[@"add"];
4 NSLog(@"Func: %@", add);
5
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


iOS7新JavaScriptCore框架入门介绍_ide_03


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

 

异常处理

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



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


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



iOS7新JavaScriptCore框架入门介绍_ide_03

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


iOS7新JavaScriptCore框架入门介绍_ide_03


 

使用Block的注意事项

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

比如上边自定义异常处理方法,就是赋给传入​​JSContext​​​对象​​con​​​,而不是其外创建的​​context​​​对象,虽然它们其实是同一个对象。这是因为Block会对内部使用的在外部定义创建的对象做强引用,而​​JSContext​​也会对被赋予的Block做强引用,这样它们之间就形成了循环引用)使得内存无法正常释放。

对于​​JSValue​​也不能直接从外部引用到Block中,因为每个​​JSValue​​上都有​​JSContext​​的引用 (​​@property(readonly, retain) JSContext *context;​ ​),​​JSContext​​再引用Block同样也会形成引用循环。