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直接实现友好互通。