在Swift中使用JavaScript的方法和技巧

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()等下标。

函数调用(Calling Functions)

我们可以将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://mp.weixin.qq.com/s?__biz=MzIzMzA4NjA5Mw==&mid=214070747&idx=1&sn=57b45fa293d0500365d9a0a4ff74a4e1#rd

和http://www.it165.net/pro/html/201404/11385.html

相关文章

软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘...
现实生活中,我们听到的声音都是时间连续的,我们称为这种信...
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿...
【Android App】实战项目之仿抖音的短视频分享App(附源码和...
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至...
因为我既对接过session、cookie,也对接过JWT,今年因为工作...