阅读更多

0顶
0踩

移动开发
本文作者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工具资源层出不穷,用于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的方法,如下表所示:



想要检索上述示例中的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

而Swift语言毕竟才诞生不久,所以并不能像Objective-C那样自如地运用下标符号,目前,Swift的方法仅能实现objectAtKeyedSubscript()和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或者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, 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/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 1906
// Ada Lovelace, born 1815
// Margaret Hamilton, born 1936

文章来源:NSHipster
  • 大小: 6.3 KB
  • 大小: 12.5 KB
来自: CSDN
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • ios开发--在Swift中使用JavaScript的方法和技巧

    本文作者Nate Cook是一位独立的Web及移动应用开发者,是继Mattt大神...在本文中,他介绍了在Swift中使用JavaScript的方法和技巧,对于iOS和Web应用工程师有着非常实用的价值,以下为译文: 在RedMonk发布的2015年1月

  • 动手写一个简单的编译器:在JavaScript中使用Swift的尾闭包语法

    首先跟大家说一下我为什么会有这个想法吧,因为最近在空闲时间学习Swift和SwiftUI的时候会经常使用到这种叫做尾闭包的语法,就觉得很有趣。同时因为很早之前看过jamiebuilds的the-super-tiny-compiler,就想着能不能...

  • 在 Swift 5 中使用 JSON 和 Codable

    作者 | Simon Ng来源 | AppCodahttps://www.appcoda.com/json-codable-swift/首先,什么是 JSON?JSON(JavaScr...

  • js中的转译_JavaScript中的填充和转译

    js中的转译JavaScript is rapidly advancing. Today it's the most popular programming/scripting language that devs use to code logic and applications and is used in umpteen number of places. The ...

  • 针对JavaScript开发人员的10个超级实用技巧

    重点 (Top highlight)As we all know, JavaScript has been changing rapidly. With the new ES2020, there are many awesome features introduced that you might want to checkout. To be honest, you can write ...

  • 使用 Swift 编写 CLI 工具的入门教程

    概述Why ScriptingWhy Swift使用 SPM 搭建开发框架项目里的文件将代码划分为 framework 和 executable构建 Xcode 项目开始动手定义程序入口...

  • Swift编程规范:保持代码优美的10个方法

    这篇Swift风格指南与你看到的其他的指南有所不同,此篇指南主要焦点集中在打印和Web展示的可读写上。我们创建此篇风格指南的目的,是为了让我们的图书、教程以及初学者套件中的代码保持优美和一致,即使我们有很多...

  • JavaScript 面向对象详解

    JavaScript面向对象

  • SwiftUI的NavigationView的基础讲解与进阶实践

    ????????关注后回复“进群”,拉你进程序员交流群????????作者丨风海铜锣君来源丨搜狐技术产品(ID:sohu-tech)本文字数:3858字预计阅读时间:12分钟前言在UIKit的框架中,我们时常...

  • html5 javascript 打印日志,JS - console命令使用详解(Chrome控制台日志技巧)

    我们只要在 JavaScript代码中使用console.log()方法,就可以将内容输出到 Chrome的控制台中显示。当然 console命令的功能远不止这些,本文对其做个总结。1,清空控制台信息如果想要清空控制台里的日志信息,除了可以...

  • javafx 教程_JavaFX技巧6:使用透明颜色

    javafx 教程 为用户界面元素选择正确的颜色始终是一个巨大的挑战,但是当您开发可重用的框架控件时,开发人员就无法控制使用它们的应用程序的外观和感觉,这甚至更... 为了解决这个问题,我在使用FlexGanttFX和FlexC...

  • JavaFX技巧6:使用透明颜色

    为用户界面元素选择正确的颜色始终是一个很大的挑战,但是当您开发可重用的框架控件时,开发人员就无法控制使用它们的应用程序的外观和感觉,这甚至更具挑战性... 为了解决此问题,我在使用FlexGanttFX和FlexCalenda...

  • 基于Selenium的Java爬虫实战(内含谷歌浏览器Chrom和Chromedriver版本116.0.5808.0)

    资源包括: 1.Java爬虫实战代码 2.selenium学习笔记 3.代码演示视频 4.谷歌浏览器chrom116.0.5808.0 chrome-linux64.zip chrome-mac-arm64.zip chrome-mac-x64.zip chrome-win32.zip chrome-win64.zip 5.谷歌浏览器驱动器Chromedriver116.0.5808.0 chromedriver-linux64.zip chromedriver-mac-arm64.zip chromedriver-mac-x64.zip chromedriver-win32.zip chromedriver-win64.zip 特别说明:Chrome 为测试版(不会自动更新) 仅适用于自动测试。若要进行常规浏览,请使用可自动更新的标准版 Chrome。)

  • 2024消费趋势报告.pdf

    2024消费趋势报告.pdf

  • PCB的电磁兼容设计+电子设计领域

    1、EMC由EMI和EMS组成 2、EMS常见的整改措施 3、干扰=共模干扰+差模干扰 4、元器件的摆放 5、缝隙影响

  • 给排水施工图全套.xdw.pdf

    给排水施工图全套.xdw

  • 基于微信小程序的旅游出行必备(后端接口ssm框架实现)-毕设项目

    毕业设计基于微信小程序的旅游出行必备(后端接口ssm框架实现)-毕设项目.zip 个人经导师指导并认可通过的高分设计项目,评审分98分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。 项目介绍: 旅游出行必备项目, 前端为微信小程序,后端接口为ssm框架实现,项目包含源码、数据库毕业设计基于微信小程序的旅游出行必备(后端接口ssm框架实现)-毕设项目.zip 个人经导师指导并认可通过的高分设计项目,评审分98分。主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。 项目主要功能: 该项目是一个基于微信小程序的旅游出行应用,结合后端SSM(Spring MVC + Spring + MyBatis)框架提供服务。主要功能包括行程规划、景点信息查询、在线预订等,旨在为用户提供便捷的旅游出行体验。特点在于利用微信小程序的便捷性,实现即用即走,同时通过后端强大的数据处理能力保证服务稳定性。技术栈涵盖微信小程序开发、Java SSM框架、数据库管理等,适合学习和作为毕业设计参考。

  • MC/00000000000000000000000000

    Want to play MC but do not want to buy can download to have a look。 It's very interested ------------------------------------------------------------------------------------------------- ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo

  • 2024嵌入式面试资料SPI子系统

    2024嵌入式面试资料SPI子系统提取方式是百度网盘分享地址

Global site tag (gtag.js) - Google Analytics