JavaScriptCore in swift

JavaScriptCore是IOS7之后苹果悄悄推出的一个框架,用于Javascript与objective-c/swift互通。让Javascript开发者可以轻松愉快地用Javascript编写应用程序。

         根据我学习的原则,新东西学习,就一起学吧,所以边学swift边学Javascript,于是就用swift来折腾折腾JavaScriptCore。

一、先看看JavaScriptCore框架的头文件:

1 #import "JSContext.h"
2 #import "JSValue.h"
3 #import "JSManagedValue.h"
4 #import "JSVirtualMachine.h"
5 #import "JSExport.h"
JSContext:Javascript的运行环境,一个JSContext就是一个Javascript的一个运行环境,也叫做作用域;个人理解,这个东西就是你有swift里的一个Javascript运行环境。
JSValue:JSContext里的不同的Javascript值都可以封闭在JSVslue的对象里,包括字符串、数值、数组、函数等,甚至还有Error以及null和undefined;同时这个类型的对象可以方便快速地转化为swift里常用的数据类型,如toBool()、toUInt32()、toArray()、toDictionary()等;简单地说就是Javascript中的数据类型与swift中的数据类型相互转化的一个中间数据类型
附:JavaScript与objective-c/swift中的数据类型对应表
 1    Objective-C type  |   JavaScript type
 2  --------------------+---------------------
 3          nil         |     undefined
 4         NSNull       |        null
 5        NSString      |       string
 6        NSNumber      |   number, boolean
 7      NSDictionary    |   Object object
 8        NSArray       |    Array object
 9         NSDate       |     Date object
10        NSBlock (1)   |   Function object (1)
11           id (2)     |   Wrapper object (2)
12         Class (3)    | Constructor object (3)
JSManagedValue:该类型主要是作为一个引用桥接,将JSValue转为JSManagedValue类型后,可以添加到JSVirtualMachine对象中,这样能够保证你在使用过程中JSValue对象不会被释放掉,当你不再需要该JSValue对象后,从JSVirtualMachine中移除该JSManagedValue对象,JSValue对象就会被释放并置空。
JSVirtualMachine:JSVirtualMachine就是一个用于保存弱引用对象的数组,加入该数组的弱引用对象因为会被该数组retain,所以保证了使用时不会被释放,当数组里的对象不再需要时,就从数组中移除,没有了引用的对象就会被系统释放;

JSExport:JSExport是一个协议,让JSContext运行环境中的JavaScript 可以识别该协议中定义的实例方法、类方法、属性等,让objective-c/swift与JavaScript能够自动交互;

二、框架的整体结构大概了解一二了,接下来动手写写代码

 var context:JSContext = JSContext()//JSContext就是一个JS运行环境
 1 context.evaluateScript("var number = 5 + 5")//往运行环境里加整形变量
 2         context.evaluateScript("var names = ['Grace','Joe','Mike']")//数组
 3         context.evaluateScript("var triple = function(value){return value * 4}")//方法
 4         var tripleNumber:JSValue = context.evaluateScript("triple(number)")
 5         println("(tripleNumber)")//40
 6         let ocnames:JSValue = context.objectForKeyedSubscript("names")//取出JS运行环境里的数组(为JSValue类型)
 7         var len = ocnames.objectForKeyedSubscript("length")//取出来的JSValue遵循JavaScript中的数组属性,所以可以直接取到JavaScript数组中的“length”取得数组中的长度
 8         println("ocnames:(ocnames) count: (len.toInt32())")//ocnames:Grace,Joe,Mike count: 3 JSValue转化为swift中的Int:len.toInt32()
 9         ocnames.setObject("himily", atIndexedSubscript: 8)
10         len = ocnames.objectForKeyedSubscript("length")
11         println("after set 'himily' at indext 8 ocnames:(ocnames) count: (len.toInt32())")//after set 'himily' at indext 8 ocnames:Grace,Joe,Mike,,,,,,himily count: 9 取出来的JSValue遵循JavaScript中的数组属性,无下标越界,自动延展数组大小
12         let firstName:JSValue = ocnames.objectAtIndexedSubscript(1)//取出JS运行环境里的数组中指定下标的元素
13         println("first name = (firstName)")//first name = Joe
14         let tripleFun:JSValue = context.objectForKeyedSubscript("triple")//取出JS运行环境中的方法
15         let tripleResult = tripleFun.callWithArguments([9])//调用JS运行环境 中的方法

代码注释已经很清楚,从上面代码我们总结几点:

1、在OC/swift里,所有JavaScript代码都需要在JavaScript运行环境(JSContext)中通过evaluateScript运行;

2、在OC/swift里,所有JavaScript中的方法、对象、属性都需要通过objectForKeyedSubscript来取得,取得所有对象均为JSValue类型

3、通过objectForKeyedSubscript取得的JavaScript中的对象,都遵循该对象在JavaScript中有的所有特性,如上述代码中数组的长度,无数组越界,自动延展的特性

4、通过objectForKeyedSubscript取得的JavaScript中的方法,均可以通过callWithArguments传入参数调用JavaScript中的方法并返回正确的结果(类型仍然为JSValue)


三、JacaScript环境中异常检测
1   //异常处理 用于检查和记录语法、类型和运行时的错误,该闭包可以检测当前context中所有JavaScript的出错
2         context.exceptionHandler = {context,exception in
3             println("JS Error:(exception)")
4         }
5         context.evaluateScript("function sqare(value1,value2){return value1 * value2")//JS Error:SyntaxError: Unexpected end of script 检测到语法出错  没有右边的“}”
6          context.evaluateScript("function sqare(value1,value2){return value1 * value2}")

在OC中exceptionHandler是block 在swift中exceptionHandler是闭包,用于检测当然JSContext运行环境中所有JavaScript代码的语法错误。如上述例子抛出了语句没结束的异常;

四、JSExport

创建一个继承JSExport协议的PersonJSExports

1 @objc protocol PersonJSExports:JSExport{
2     var firstName:String{get set}
3     var lastName:String{get set}
4     var birthYear:NSNumber?{get set}
5 }

创建一个Person,让它继承PersonJSExports协议

 1 @objc class Person:NSObject,PersonJSExports{
 2     dynamic var firstName:String
 3     dynamic var lastName:String
 4     dynamic var birthYear:NSNumber?
 5     init(firstName:String,lastName:String){
 6         self.firstName = firstName
 7         self.lastName = lastName
 8         self.birthYear = NSNumber(int: 1986)
 9     }
10 }

创建一个Person的对象实例,记其成为contex环境中的“Person”

 1     var personal:Person = Person(firstName: "lily", lastName: "king")
 2         context.setObject(personal, forKeyedSubscript:"Person")//把Person类导出到JS中去
 3         var p:JSValue = context.objectForKeyedSubscript("Person")
 4         var first:JSValue = p.objectForKeyedSubscript("firstName")
 5         var last:JSValue = p.objectForKeyedSubscript("lastName")
 6         var year:JSValue = p.objectForKeyedSubscript("birthYear")
 7 
 8         println("p.first:(first) p.last:(last) p.year:(year)")//这里面有个incorporate一词值得推敲,经过验证只有直接继承了JSExport的中自定义协议(@protocol)(定义的属性、方法)才能在JSContext中访问到 分两种情况 1.如果PersonJSExports(直接继承JSExport)里有定义firstName、lastName则打印出 p.first:lily p.last:king 2、如果PersonJSExports里没有定义firstName、lastName则打印出p.first:undefined p.last:undefined
 9         personal.firstName = "lucy"
10         personal.lastName = "queen"
11         personal.birthYear = NSNumber(int: 1999)
12         var first1:JSValue = p.objectForKeyedSubscript("firstName")
13         var last1:JSValue = p.objectForKeyedSubscript("lastName")
14         var year1:JSValue = p.objectForKeyedSubscript("birthYear")
15         println("aftter change p.first:(first1) p.last:(last1) p.year:(year1)")//经过验证只有直接继承了JSExport的中自定义协议(@protocol)(定义的属性、方法)才能在JSContext中访问到 分两种情况 1.如果PersonJSExports(直接继承JSExport)里有定义firstName、lastName则打印出 p.first:lucy p.last:queen 2、如果PersonJSExports里没有定义firstName、lastName则打印出aftter change p.first:undefined p.last:undefined full name undefined

由上面代码的输出可以看到,只有直接继承了JSExport的中自定义协议(@protocol)(定义的属性、方法)才能在JSContext中访问到。

关于JSExport中定义的方法,用OC来写一段

定义一个UIButtonJsexport让其直接继承JSExport协议

1 @protocol UIButtonJsexport <JSExport>
2 
3 - (void)setTitle:(NSString *)title forState:(UIControlState)state;
4 
5 @end

创建 一个button,同时把UIButton添加一个UIButtonJsexport协议class_addProtocol([UIButton class], @protocol(UIButtonJsexport));

 1  class_addProtocol([UIButton class], @protocol(UIButtonJsexport));
 2     UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 40)];
 3     [button setTitle:@"objective-c" forState:UIControlStateNormal];
 4     [button setBackgroundColor:[UIColor blueColor]];
 5     [self.view addSubview:button];
 6     context = [[JSContext alloc] init];
 7     [context setObject:button forKeyedSubscript:@"button"];
 8     context.exceptionHandler = ^(JSContext *con,JSValue *exception){
 9         con.exception = exception;
10         NSLog(@"JS error:%@",exception);
11     };
12     [button addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];

点击的时候在context中改变button的title

1 -(void)click
2 {
3     [context evaluateScript:@"button.setTitleForState('JavaScript',0)"];
4 }

当运行点击UIButton时就会看到button的title被改变了,也证明了对于已定义的类,也可以在运行时添加神奇的JSExport协议让它们可以在Objective-C和JavaScript直接实现友好互通。

原文地址:https://www.cnblogs.com/yanyan1119/p/4262962.html