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

在Swift中使用JavaScript的方法和技巧,第1张

概述本文作者Nate Cook是一位独立的Web及移动应用开发者,是继Mattt大神之后NSHipster的主要维护者,也是非常知名活跃的Swift博主,并且还是支持自动生成Swift在线文档的SwiftDoc.org网站创造者。在本文中,他介绍了在Swift中使用JavaScript的方法技巧,对于iOS和Web应用工程师有着非常实用的价值,以下为译文: 在RedMonk发布的2015年1月编程语

本文作者Nate Cook是一位独立的Web及移动应用开发者,是继Mattt大神之后NSHipster的主要维护者,也是非常知名活跃的Swift博主,并且还是支持自动生成Swift在线文档的SwiftDoc.org网站创造者。在本文中,他介绍了在Swift中使用JavaScript的方法和技巧,对于iOS和Web应用工程师有着非常实用的价值,以下为译文:


在RedMonk发布的2015年1月编程语言排行榜中,Swift采纳率排名迅速飙升,从刚刚面世时的68位跃至22位,Objective-C仍然稳居top
10,而JavaScript则凭借着其在iOS平台上原生体验优势成为了年度最火热的编程语言。



而早在2013年苹果发布的OS X Mavericks和iOS 7两大系统中便均已加入了JavaScriptCore框架,能够让开发者轻松、快捷、安全地使用JavaScript语言编写应用。不论叫好叫骂,JavaScript霸主地位已成事实。开发者们趋之若鹜,Js工具资源层出不穷,用于OSX和iOS系统等高速虚拟机也蓬勃发展起来。


@H_404_34@jscontext/JsValue

@H_404_34@

jscontext即JavaScript代码的运行环境。一个Context就是一个JavaScript代码执行的环境,也叫作用域。当在浏览器中运行JavaScript代码时,jscontext就相当于一个窗口,能轻松执行创建变量、运算乃至定义函数等的JavaScript代码:

//Objective-C
jscontext *context = [[jscontext alloc] init];
[context evaluateScript:@"var num = 5 + 5"];
[context evaluateScript:@"var names = ['Grace','Ada','Margaret']"];
[context evaluateScript:@"var triple = function(value) { return value * 3 }"];
JsValue *tripleNum = [context evaluateScript:@"triple(num)"]; 复制代码 //Swift
let context = jscontext()
context.evaluateScript("var num = 5 + 5")
context.evaluateScript("var names = ['Grace','Margaret']")
context.evaluateScript("var triple = function(value) { return value * 3 }")
let tripleNum: JsValue = context.evaluateScript("triple(num)") 复制代码

像JavaScript这类动态语言需要一个动态类型(Dynamic Type), 所以正如代码最后一行所示,jscontext里不同的值均封装在JsValue对象中,包括字符串、数值、数组、函数等,甚至还有Error以及null和undefined。


JsValue包含了一系列用于获取Underlying Value的方法,如下表所示:




想要检索上述示例中的tripleNum值,只需使用相应的方法即可:

//Objective-C
NSLog(@"Tripled: %d",[tripleNum toInt32]);
// Tripled: 30 复制代码 //Swift
println("Tripled: \(tripleNum.toInt32())")
// Tripled: 30 复制代码

@H_404_34@下标值(Subscripting Values)

@H_404_34@

通过在jscontext和JsValue实例中使用下标符号可以轻松获取上下文环境中已存在的值。其中,jscontext放入对象和数组的只能是字符串下标,而JsValue则可以是字符串或整数下标。

//Objective-C
JsValue *names = context[@"names"];
JsValue *initialname = names[0];
NSLog(@"The first name: %@",[initialname toString]);
// The first name: Grace 复制代码 //Swift
let names = context.objectForKeyedSubscript("names")
let initialname = names.objectAtIndexedSubscript(0)
println("The first name: \(initialname.toString())")
// The first name: Grace 复制代码

而Swift语言毕竟才诞生不久,所以并不能像Objective-C那样自如地运用下标符号,目前,Swift的方法仅能实现objectAtKeyedSubscript()和objectAtIndexedSubscript()等下标。


@H_404_34@函数调用(Calling Functions)

@H_404_34@

我们可以将Foundation类作为参数,从Objective-C/Swift代码上直接调用封装在JsValue的JavaScript函数。这里,JavaScriptCore再次发挥了衔接作用。

//Objective-C
JsValue *tripleFunction = context[@"triple"];
JsValue *result = [tripleFunction callWithArguments:@[@5] ];
NSLog(@"Five tripled: %d",[result toInt32]); 复制代码 //Swift
let tripleFunction = context.objectForKeyedSubscript("triple")
let result = tripleFunction.callWithArguments([5])
println("Five tripled: \(result.toInt32())") 复制代码

@H_404_34@异常处理(Exception Handling)

@H_404_34@

jscontext还有一个独门绝技,就是通过设定上下文环境中exceptionHandler的属性,可以检查和记录语法、类型以及出现的运行时错误。exceptionHandler是一个回调处理程序,主要接收jscontext的reference,进行异常情况处理。

//Objective-C
context.exceptionHandler = ^(jscontext *context,JsValue *exception) {
NSLog(@"Js Error: %@",exception);
};
[context evaluateScript:@"function multiply(value1,value2) { return value1 * value2 "];
// Js Error: SyntaxError: Unexpected end of script 复制代码 @H_404_34@ //Swift
context.exceptionHandler = { context,exception in
println("Js Error: \(exception)")
}
context.evaluateScript("function multiply(value1,value2) { return value1 * value2 ")
// Js Error: SyntaxError: Unexpected end of script 复制代码
@H_404_34@JavaScript函数调用

了解了从JavaScript环境中获取不同值以及调用函数的方法,那么反过来,如何在JavaScript环境中获取Objective-C或者Swift定义的自定义对象和方法呢?要从jscontext中获取本地客户端代码,主要有两种途径,分别为Blocks和JsExport协议。


Blocks (块)


在jscontext中,如果Objective-C代码块赋值为一个标识符,JavaScriptCore就会自动将其封装在JavaScript函数中,因而在JavaScript上使用Foundation和Cocoa类就更方便些——这再次验证了JavaScriptCore强大的衔接作用。现在CFStringtransform也能在JavaScript上使用了,如下所示:

//Objective-C
context[@"simplifyString"] = ^(Nsstring *input) {
NSMutableString *mutableString = [input mutablecopy];
CFStringtransform((__brIDge CFMutableStringRef)mutableString,NulL,kcfStringtransformTolatin,NO);
CFStringtransform((__brIDge CFMutableStringRef)mutableString,kcfStringtransformStripCombiningMarks,NO);
return mutableString;
};
NSLog(@"%@",[context evaluateScript:@"simplifyString('안녕하새요!')"]); 复制代码 //Swift
let simplifyString: @objc_block String -> String = { input in
var mutableString = NSMutableString(string: input) as CFMutableStringRef
CFStringtransform(mutableString,nil,Boolean(0))
CFStringtransform(mutableString,Boolean(0))
return mutableString
}
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交互。文章稍后将介绍具体的实践过程。


@H_404_34@JavaScriptCore实践

@H_404_34@

我们可以通过一些例子更好地了解上述技巧的使用方法。先定义一个遵循JsExport子协议PersonjsExport的Person model,再用JavaScript在JsON中创建和填入实例。有整个JVM,还要NSJsONSerialization干什么?


PersonjsExports和Person


Person类执行的PersonjsExports协议具体规定了可用的JavaScript属性。,在创建时,类方法必不可少,因为JavaScriptCore并不适用于初始化转换,我们不能像对待原生的JavaScript类型那样使用var person = new Person()。

//Objective-C
// in Person.h -----------------
@class Person;
@protocol PersonjsExports <JsExport>
@property (nonatomic,copy) Nsstring *firstname;
@property (nonatomic,copy) Nsstring *lastname;
@property NSInteger agetoday;
- (Nsstring *)getFullname;
// create and return a new Person instance with `firstname` and `lastname`
+ (instancetype)createWithFirstname:(Nsstring *)firstname lastname:(Nsstring *)lastname;
@end
@interface Person : NSObject <PersonjsExports>
@property (nonatomic,copy) Nsstring *lastname;
@property NSInteger agetoday;
@end
// in Person.m -----------------
@implementation Person
- (Nsstring *)getFullname {
return [Nsstring stringWithFormat:@"%@ %@",self.firstname,self.lastname];
}
+ (instancetype) createWithFirstname:(Nsstring *)firstname lastname:(Nsstring *)lastname {
Person *person = [[Person alloc] init];
person.firstname = firstname;
person.lastname = lastname;
return person;
}
@end 复制代码 //Swift
// Custom protocol must be declared with `@objc`
@objc protocol PersonjsExports : JsExport {
var firstname: String { get set }
var lastname: String { get set }
var birthYear: NSNumber? { get set }
func getFullname() -> String
/// create and return a new Person instance with `firstname` and `lastname`
class func createWithFirstname(firstname: String,lastname: String) -> Person
}
// Custom class must inherit from `NSObject`
@objc class Person : NSObject,PersonjsExports {
// propertIEs must be declared as `dynamic`
dynamic var firstname: String
dynamic var lastname: String
dynamic var birthYear: NSNumber?
init(firstname: String,lastname: String) {
self.firstname = firstname
self.lastname = lastname
}
class func createWithFirstname(firstname: String,lastname: String) -> Person {
return Person(firstname: firstname,lastname: lastname)
}
func getFullname() -> String {
return "\(firstname) \(lastname)"
}
} 复制代码
//Swift
// export Person class
context.setobject(Person.self,forKeyedSubscript: "Person")
// load Mustache.Js
if let mustacheJsstring = String(contentsOffile:...,enCoding:NSUTF8StringEnCoding,error:nil) {
context.evaluateScript(mustacheJsstring)
} 复制代码
配置jscontext

创建Person类之后,需要先将其导出到JavaScript环境中去,同时还需导入Mustache Js库,以便对Person对象应用模板。

//Objective-C
// export Person class
context[@"Person"] = [Person class];
// load Mustache.Js
Nsstring *mustacheJsstring = [Nsstring stringWithContentsOffile:... 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 }
] 复制代码 //JavaScript
var loadPeopleFromJsON = function(JsonString) {
var data = JsON.parse(JsonString);
var people = [];
for (i = 0; i < data.length; i++) {
var person = Person.createWithFirstnameLastname(data[i].first,data[i].last);
person.birthYear = data[i].year;
people.push(person);
}
return people;
} 复制代码
动手一试

现在你只需加载JsON数据,并在jscontext中调用,将其解析到Person对象数组中,再用Mustache模板渲染即可:

//Objective-C
// get JsON string
Nsstring *peopleJsON = [Nsstring stringWithContentsOffile:... enCoding:NSUTF8StringEnCoding error:nil];
// get load function
JsValue *load = context[@"loadPeopleFromJsON"];
// call with JsON and convert to an NSArray
JsValue *loadResult = [load callWithArguments:@[peopleJsON]];
NSArray *people = [loadResult toArray];
// get rendering function and create template
JsValue *mustacheRender = context[@"Mustache"][@"render"];
Nsstring *template = @"{{getFullname}},born {{birthYear}}";
// loop through people and render Person object as string
for (Person *person in people) {
NSLog(@"%@",[mustacheRender callWithArguments:@[template,person]]);
}
// Output:
// Grace Hopper,born 1906
// Ada lovelace,born 1815
// Margaret Hamilton,born 1936 复制代码 //Swift
// get JsON string
if let peopleJsON = Nsstring(contentsOffile:...,enCoding: NSUTF8StringEnCoding,error: nil) {
// get load function
let load = context.objectForKeyedSubscript("loadPeopleFromJsON")
// call with JsON and convert to an array of `Person`
if let people = load.callWithArguments([peopleJsON]).toArray() as? [Person] {

// get rendering function and create template
let mustacheRender = context.objectForKeyedSubscript("Mustache").objectForKeyedSubscript("render")
let template = "{{getFullname}},born {{birthYear}}"

// loop through people and render Person object as string
for person in people {
println(mustacheRender.callWithArguments([template,person]))
}
}
}
// Output:
// Grace Hopper,born 1936 复制代码

本文援引自 码农网

总结

以上是内存溢出为你收集整理的在Swift中使用JavaScript的方法和技巧全部内容,希望文章能够帮你解决在Swift中使用JavaScript的方法和技巧所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1091492.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存