他牵线了在斯维夫特中使用JavaScript的方式和才能

正文小编Nate
Cook是壹位单身的Web及活动使用开垦者,是继Mattt大神之后NSHipster的机要维护者,也是格外国资本深活跃的斯维夫特博主,并且依然协理自动生成Swift在线文书档案的SwiftDoc.org网址创立者。在本文中,他介绍了在斯威夫特中选取JavaScript的法子和本领,对于iOS和Web应用程序猿有着丰盛实用的价值,以下为译文:

在RedMonk发表的2016年6月编制程序语言排名的榜单中,斯威夫特接受率排行快速攀升,从刚刚现身时的陆拾陆人跃至二十四人,Objective-C仍旧稳居TOP
10,而JavaScript则依靠着其在iOS平台上原生体验优势成为了年度最销路好的编制程序语言。

图片 1

而早在二零一一年苹果公布的OS X Mavericks和iOS
7两概况系中便均已参与了JavaScriptCore框架,可以让开采者轻巧、火速、安全地行使JavaScript语言编写应用。不论叫好叫骂,JavaScript霸主地位已成事实。开荒者们源源不断,JS工具能源更仆难数,用于OS
X和iOS系统等急忙虚构机也如日方升起来。

JSContext/JSValue

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', 'Ada', '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的措施,如下表所示:

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]!
Object toObjecttoObjectOfClass: custom type custom type

想要检索上述示范中的tripleNum值,只需接纳相应的办法就可以:

//Objective-C
NSLog(@"Tripled: %d", [tripleNum toInt32]);
// Tripled: 30

//Swift
println("Tripled: (tripleNum.toInt32())")
// Tripled: 30

下标值(Subscripting Values)

因而在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

而斯维夫特语言究竟才出生不久,所以并无法像Objective-C那样自如地运用下标符号,近期,斯维夫特的秘诀仅能兑现objectAtKeyedSubscript(State of Qatar和objectAtIndexedSubscript(卡塔尔(قطر‎等下标。

函数调用(Calling Functions)

小编们可以将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())")

非常管理(Exception Handling)

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

//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

JavaScript函数调用

打听了从JavaScript景况中收获分裂值以至调用函数的方法,那么反过来,怎么着在JavaScript意况中获得Objective-C或然斯威夫特定义的自定义对象和情势吧?要从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, NULL, 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, kCFStringTransformToLatin, Boolean(0))
    CFStringTransform(mutableString, nil, kCFStringTransformStripCombiningMarks, 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人机联作。随笔稍后将介绍具体的推行进度。

JavaScriptCore实践

咱俩得以由此某件事例更加好地询问上述本事的运用方式。先定义多少个遵纪守法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 *firstName;
    @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)"
    }
}
  • 配置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];

//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)
}
  • JavaScript数据&处理

以下轻巧列出贰个JSON楷模,以致用JSON来创建新Person实例。

介怀:JavaScriptCore完成了Objective-C/斯维夫特的方法名和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 1906
// Ada Lovelace, born 1815
// Margaret Hamilton, born 1936

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website