JSContext/JSValue
JSContext即JavaScript代码的运行环境。一个Context就是一个JavaScript代码执行的环境,也叫作用域。当在浏览器中运行JavaScript代码时,JSContext就相当于一个窗口,能轻松执行创建变量、运算乃至定义函数等的JavaScript代码:
//Objective-CJSContext*context=[[JSContextalloc]init];[contextevaluateScript:@"varnum=5+5"];[contextevaluateScript:@"varnames=['Grace','Ada','Margaret']"];[contextevaluateScript:@"vartriple=function(value){returnvalue*3}"];JSValue*tripleNum=[contextevaluateScript:@"triple(num)"];
//Swiftletcontext=JSContext()context.evaluateScript("varnum=5+5")context.evaluateScript("varnames=['Grace','Margaret']")context.evaluateScript("vartriple=function(value){returnvalue*3}")lettripleNum:JSValue=context.evaluateScript("triple(num)")
像JavaScript这类动态语言需要一个动态类型(Dynamic Type), 所以正如代码最后一行所示,JSContext里不同的值均封装在JSValue对象中,包括字符串、数值、数组、函数等,甚至还有Error以及null和undefined。
JSValue包含了一系列用于获取Underlying Value的方法,如下表所示:
JavaScript Type |
JSValue method |
Objective-C Type |
Swift Type |
string | toString | NSString | String! |
boolean | toBool | BOOL | Bool |
number | toNumbertoDoubletoInt32 toUInt32 |
NSNumberdoubleint32_t uint32_t |
NSNumber!DoubleInt32 UInt32 |
Date | toDate | NSDate | NSDate! |
Array | toArray | NSArray | [AnyObject]! |
Object | toDictionary | NSDictionary | [NSObject : AnyObject]! |
toObjecttoObjectOfClass: | custom type | custom type |
想要检索上述示例中的tripleNum值,只需使用相应的方法即可:
//Objective-CNSLog(@"Tripled:%d",[tripleNumtoInt32]);//Tripled:30
//Swiftprintln("Tripled:\(tripleNum.toInt32())")//Tripled:30
下标值(Subscripting Values)
通过在JSContext和JSValue实例中使用下标符号可以轻松获取上下文环境中已存在的值。其中,JSContext放入对象和数组的只能是字符串下标,而JSValue则可以是字符串或整数下标。
//Objective-CJSValue*names=context[@"names"];JSValue*initialName=names[0];NSLog(@"Thefirstname:%@",[initialNametoString]);//Thefirstname:Grace
//Swiftletnames=context.objectForKeyedSubscript("names")letinitialName=names.objectAtIndexedSubscript(0)println("Thefirstname:\(initialName.toString())")//Thefirstname:Grace
而Swift语言毕竟才诞生不久,所以并不能像Objective-C那样自如地运用下标符号,目前,Swift的方法仅能实现objectAtKeyedSubscript()和objectAtIndexedSubscript()等下标。
我们可以将Foundation类作为参数,从Objective-C/Swift代码上直接调用封装在JSValue的JavaScript函数。这里,JavaScriptCore再次发挥了衔接作用。
//Objective-CJSValue*tripleFunction=context[@"triple"];JSValue*result=[tripleFunctioncallWithArguments:@[@5]];NSLog(@"Fivetripled:%d",[resulttoInt32]);
//SwiftlettripleFunction=context.objectForKeyedSubscript("triple")letresult=tripleFunction.callWithArguments([5])println("Fivetripled:\(result.toInt32())")
异常处理(Exception Handling)
JSContext还有一个独门绝技,就是通过设定上下文环境中exceptionHandler的属性,可以检查和记录语法、类型以及出现的运行时错误。exceptionHandler是一个回调处理程序,主要接收JSContext的reference,进行异常情况处理。
//Objective-Ccontext.exceptionHandler=^(JSContext*context,JSValue*exception){ NSLog(@"JSError:%@",exception);};[contextevaluateScript:@"functionmultiply(value1,value2){returnvalue1*value2"];//JSError:SyntaxError:Unexpectedendofscript
//Swiftcontext.exceptionHandler={context,exceptionin println("JSError:\(exception)")}context.evaluateScript("functionmultiply(value1,value2){returnvalue1*value2")//JSError:SyntaxError:Unexpectedendofscript
JavaScript函数调用
了解了从JavaScript环境中获取不同值以及调用函数的方法,那么反过来,如何在JavaScript环境中获取Objective-C或者Swift定义的自定义对象和方法呢?要从JSContext中获取本地客户端代码,主要有两种途径,分别为Blocks和JSExport协议。
Blocks (块)
在JSContext中,如果Objective-C代码块赋值为一个标识符,JavaScriptCore就会自动将其封装在JavaScript函数中,因而在JavaScript上使用Foundation和Cocoa类就更方便些——这再次验证了JavaScriptCore强大的衔接作用。现在CFStringTransform也能在JavaScript上使用了,如下所示:
//Objective-Ccontext[@"simplifyString"]=^(NSString*input){ NSMutableString*mutableString=[inputmutableCopy]; CFStringTransform((__bridgeCFMutableStringRef)mutableString,NULL,kcfStringTransformToLatin,NO); CFStringTransform((__bridgeCFMutableStringRef)mutableString,kcfStringTransformStripCombiningMarks,NO); returnmutableString;};NSLog(@"%@",[contextevaluateScript:@"simplifyString('안녕하새요!')"]);
//SwiftletsimplifyString:@objc_blockString->String={inputin varmutableString=NSMutableString(string:input)asCFMutableStringRef CFStringTransform(mutableString,nil,Boolean(0)) CFStringTransform(mutableString,Boolean(0)) returnmutableString}context.setObject(unsafeBitCast(simplifyString,AnyObject.self),forKeyedSubscript:"simplifyString")println(context.evaluateScript("simplifyString('안녕하새요!')"))//annyeonghaSAEyo!
需要注意的是,Swift的speedbump只适用于Objective-C block,对Swift闭包无用。要在一个JSContext里使用闭包,有两个步骤:一是用@objc_block来声明,二是将Swift的knuckle-whitening unsafeBitCast()函数转换为 AnyObject。
内存管理(Memory Management)
代码块可以捕获变量引用,而JSContext所有变量的强引用都保留在JSContext中,所以要注意避免循环强引用问题。另外,也不要在代码块中捕获JSContext或任何JSValues,建议使用[JSContext currentContext]来获取当前的Context对象,根据具体需求将值当做参数传入block中。
JSExport协议
借助JSExport协议也可以在JavaScript上使用自定义对象。在JSExport协议中声明的实例方法、类方法,不论属性,都能自动与JavaScrip交互。文章稍后将介绍具体的实践过程。
JavaScriptCore实践
我们可以通过一些例子更好地了解上述技巧的使用方法。先定义一个遵循JSExport子协议PersonJSExport的Person model,再用JavaScript在JSON中创建和填入实例。有整个JVM,还要NSJSONSerialization干什么?
PersonJSExports和Person
Person类执行的PersonJSExports协议具体规定了可用的JavaScript属性。,在创建时,类方法必不可少,因为JavaScriptCore并不适用于初始化转换,我们不能像对待原生的JavaScript类型那样使用var person = new Person()。
//Objective-C//inPerson.h-----------------@classPerson;@protocolPersonJSExports<JSExport> @property(nonatomic,copy)NSString*firstName; @property(nonatomic,copy)NSString*lastName; @propertyNSIntegerageToday; -(NSString*)getFullName; //createandreturnanewPersoninstancewith`firstName`and`lastName` +(instancetype)createWithFirstName:(NSString*)firstNamelastName:(NSString*)lastName;@end@interfacePerson:NSObject<PersonJSExports> @property(nonatomic,copy)NSString*lastName; @propertyNSIntegerageToday;@end//inPerson.m-----------------@implementationPerson-(NSString*)getFullName{ return[NSStringstringWithFormat:@"%@%@",self.firstName,self.lastName];}+(instancetype)createWithFirstName:(NSString*)firstNamelastName:(NSString*)lastName{ Person*person=[[Personalloc]init]; person.firstName=firstName; person.lastName=lastName; returnperson;}@end
//Swift//Customprotocolmustbedeclaredwith`@objc`@objcprotocolPersonJSExports:JSExport{ varfirstName:String{getset} varlastName:String{getset} varbirthYear:NSNumber?{getset} funcgetFullName()->String ///createandreturnanewPersoninstancewith`firstName`and`lastName` classfunccreateWithFirstName(firstName:String,lastName:String)->Person}//Customclassmustinheritfrom`NSObject`@objcclassPerson:NSObject,PersonJSExports{ //propertiesmustbedeclaredas`dynamic` dynamicvarfirstName:String dynamicvarlastName:String dynamicvarbirthYear:NSNumber? init(firstName:String,lastName:String){ self.firstName=firstNameself.lastName=lastName} classfunccreateWithFirstName(firstName:String,lastName:String)->Person{ returnPerson(firstName:firstName,lastName:lastName) } funcgetFullName()->String{ return"\(firstName)\(lastName)" }}
配置JSContext
创建Person类之后,需要先将其导出到JavaScript环境中去,同时还需导入Mustache JS库,以便对Person对象应用模板。
//Objective-C//exportPersonclasscontext[@"Person"]=[Personclass];//loadMustache.jsNSString*mustacheJSString=[NSStringstringWithContentsOfFile:...encoding:NSUTF8StringEncodingerror:nil];[contextevaluateScript:mustacheJSString];
//Swift//exportPersonclasscontext.setObject(Person.self,forKeyedSubscript:"Person")//loadMustache.jsifletmustacheJSString=String(contentsOfFile:...,encoding:NSUTF8StringEncoding,error:nil){ context.evaluateScript(mustacheJSString)}
JavaScript数据&处理
以下简单列出一个JSON范例,以及用JSON来创建新Person实例。
注意:JavaScriptCore实现了Objective-C/Swift的方法名和JavaScript代码交互。因为JavaScript没有命名好的参数,任何额外的参数名称都采取驼峰命名法(Camel-Case),并附加到函数名称上。在此示例中,Objective-C的方法createWithFirstName:lastName:在JavaScript中则变成了createWithFirstNameLastName()。
//JSON[ {"first":"Grace","last":"Hopper","year":1906},{"first":"Ada","last":"Lovelace","year":1815},{"first":"Margaret","last":"Hamilton","year":1936}]
//JavaScriptvarloadPeopleFromJSON=function(jsonString){ vardata=JSON.parse(jsonString); varpeople=[]; for(i=0;i<data.length;i++){ varperson=Person.createWithFirstNameLastName(data[i].first,data[i].last); person.birthYear=data[i].year; people.push(person); } returnpeople;}
动手一试
现在你只需加载JSON数据,并在JSContext中调用,将其解析到Person对象数组中,再用Mustache模板渲染即可:
//Objective-C//getJSONstringNSString*peopleJSON=[NSStringstringWithContentsOfFile:...encoding:NSUTF8StringEncodingerror:nil];//getloadfunctionJSValue*load=context[@"loadPeopleFromJSON"];//callwithJSONandconverttoanNSArrayJSValue*loadResult=[loadcallWithArguments:@[peopleJSON]];NSArray*people=[loadResulttoArray];//getrenderingfunctionandcreatetemplateJSValue*mustacheRender=context[@"Mustache"][@"render"];NSString*template=@"{{getFullName}},born{{birthYear}}";//loopthroughpeopleandrenderPersonobjectasstringfor(Person*personinpeople){ NSLog(@"%@",[mustacheRendercallWithArguments:@[template,person]]);}//Output://GraceHopper,born1906//AdaLovelace,born1815//MargaretHamilton,born1936
//Swift//getJSONstringifletpeopleJSON=NSString(contentsOfFile:...,error:nil){ //getloadfunction letload=context.objectForKeyedSubscript("loadPeopleFromJSON") //callwithJSONandconverttoanarrayof`Person` ifletpeople=load.callWithArguments([peopleJSON]).toArray()as?[Person]{ //getrenderingfunctionandcreatetemplate letmustacheRender=context.objectForKeyedSubscript("Mustache").objectForKeyedSubscript("render") lettemplate="{{getFullName}},born{{birthYear}}" //loopthroughpeopleandrenderPersonobjectasstring forpersoninpeople{ println(mustacheRender.callWithArguments([template,person])) } }}//Output://GraceHopper,born1936
和http://www.it165.net/pro/html/201404/11385.html