Swift 2.2 的新特性
Swift 2.2 带来了新的语法、新功能以及一些弃用。这是今年晚些时候 Swift 3 发布的临时版本,Swift 3 将带来更大的变化,Swift 2.2 中的变化与 Swift 3 的更广泛目标相一致,即通过添加缺失的功能、改进现有功能以及删除语言中不再需要的功能,逐步稳定核心语言和标准库。Swift 2.2 中的所有更改都经过了社区驱动的 Swift 演化过程——自从 Swift 几个月前开源以来,已经提交、审查和接受了 30 多个提案。
Swift 2.2 中的更改将直接影响您的代码,本文将引导您了解已更改的内容和原因,并提供代码示例,以帮助您快速迁移到新的 Swift 2.2 语法。
提醒一下,“弃用”意味着不再推荐使用某个函数或语言功能,并且将在稍后完全删除。在实践中,这意味着 Swift 今天会发出编译器警告,将来(很可能是 Swift 3)会发出编译器错误。
编译时 Swift 版本检查
Swift 的持续发展有时会让应用程序开发人员难以跟上,但对于库的开发人员来说,这甚至更加困难——如果您有成千上万的人使用您的库,您可能需要支持多个 Swift 版本,以确保每个人都可以使用您的工作,无论他们使用的是哪个版本。
Swift 2.2 引入了一个新的编译器指令,使跨版本兼容性变得轻而易举:您现在可以指定代码块,只有当编译器支持特定的 Swift 语言版本时才应读取这些代码块。例如
#if swift(>=3.0)
print("Running Swift 3.0 or later")
#else
print("Running Swift 2.2 or earlier")
#endif
这与 Swift 2 中引入的 #available 语法不同,因为后者是运行时检查——这项新功能是编译时检查,因此未通过语言版本检查的代码实际上是不可见的。如果您愿意,可以编写如下代码
#if swift(>=3.0)
print("Running Swift 3.0 or later")
#else
BRING BACK WASH HE WAS MY FAVORITE
#endif
只要 Swift 编译器目标版本为 3.0 或更高版本,这将可以正常编译,因为编译器会忽略大写字母的消息。
警告:此功能目前尚不可用,因为 Swift 2.1 编译器会无法处理 #if swift(>=2.2) ——它不知道这意味着什么。但是,一旦 Swift 3.0 可用,并且对于所有未来版本,编译时 Swift 版本检查将成为您工具包中的有用补充。
有关更多信息,请参阅此更改的 Swift 演化提案。
编译时检查的选择器
在 Swift 2.1 中,如下代码可以毫无问题地编译
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .Add, target: self,
action: "addNewFireflyRefernce")
}
func addNewFireflyReference() {
gratuitousReferences.append("We should start dealing in black-market beagles.")
}
代码本身在语法上是正确的,但应用程序会崩溃,因为导航栏按钮调用了一个方法 addNewFireflyRefernce() ——“reference”中少了一个 E。这些简单的拼写错误很容易引入 bug,因此 Swift 2.2 弃用了使用字符串作为选择器,而是引入了新的语法:#selector。
使用 #selector 将在编译时检查您的代码,以确保您要调用的方法实际存在。更棒的是,如果该方法不存在,您将收到一个编译错误:Xcode 将拒绝构建您的应用程序,从而消除另一个可能的 bug 来源。
以下是使用 #selector 重写的先前代码示例
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .Add, target: self,
action: #selector(addNewFireflyRefernce))
}
func addNewFireflyReference() {
gratuitousReferences.append("Curse your sudden but inevitable betrayal!")
}
构建该代码时,编译器将返回错误“使用未解析的标识符 ‘addNewFireflyRefernce’”—太棒了!
有关更多信息,请参阅 Swift 演化提案 或阅读 swift-evolution-announce 帖子,其中详细说明了理由。
更多关键字作为参数标签
Swift 有很多关键字:像 class、func、let 和 public 这样的小东西,它们具有特殊的含义,不能用作标识符。Swift 一直允许您使用关键字作为参数标签,但前提是将它们放在反引号中,如下所示
func visitCity(name: String, `in` state: String) {
print("I'm going to visit \(name) in \(state)")
}
visitCity("Nashville", `in`: "Tennessee")
从 Swift 2.2 开始,任何关键字都可以用作参数标签,但 inout、var 和 let 除外。如果您有代码在反引号中使用了关键字,您将获得 Xcode Fix-it 来删除它们。因此,现在可以编写如下代码
func visitCity(name: String, in state: String) {
print("I'm going to visit \(name) in \(state)")
}
visitCity("Nashville", in: "Tennessee")
有关更多信息,请参阅此更改的 Swift 演化提案。
元组比较是内置的
元组是 Swift 中的基本数据类型,并带来了许多好处——最重要的是能够从函数返回多个值。Swift 2.2 引入了比较两个元组是否相等的能力,这意味着它将检查一个元组中的每个元素与另一个元组中的匹配元素,如果所有元素都匹配,则报告为 true。
例如,以下代码将打印“No match”
let singer = (first: "Taylor", last: "Swift")
let alien = (first: "Justin", last: "Bieber")
if singer == alien {
print("They match! That explains why you never see them together…")
} else {
print("No match.")
}
Swift 2.2 元组比较最多可处理 6 个arity,这是一种花哨的说法,即只要元组包含不超过六个元素,就可以进行比较。
一个警告:Swift 2.2 在检查相等性时会忽略您的元素名称,因此在下面的代码中,singer 和 bird 将被视为相等
let singer = (first: "Taylor", last: "Swift")
let bird = (name: "Taylor", breed: "Swift")
if singer == bird {
print("This explains why she sings so well.")
} else {
print("No match.")
}
有关更多信息,请参阅此更改的 Swift 演化提案。
元组 splat 语法已弃用
继续讨论元组,Swift 2.2 弃用了一个很少使用的功能,我提到它只是因为它的名字很棒。在 Swift 2.1 和更早版本中,可以使用精心制作的元组来填充函数的参数。因此,如果您有一个接受两个参数的函数,则可以使用包含两个元素的元组来调用它,只要该元组具有正确的类型和元素名称即可。例如
func describePerson(name: String, age: Int) {
print("\(name) is \(age) years old")
}
let person = ("Malcolm Reynolds", age: 49)
describePerson(person)
这种语法——被亲切地称为“元组 splat 语法”——与 Swift 惯用的自文档化、可读风格背道而驰,因此在 Swift 2.2 中已弃用。
有关更多信息,请参阅 Swift 演化提案 或阅读 swift-evolution-announce 帖子,其中详细说明了理由。
C 风格的 for 循环已弃用 for 循环已弃用 部分的永久链接" href="#c-style-for-loops-are-deprecated">
即使 Swift 有几个惯用的循环选项,C 风格的 for 循环仍然是语言的一部分,偶尔也会使用。例如
for var i = 0; i < 10; i++ {
print(i)
}
这些在 Swift 2.2 中已弃用,并将在 Swift 3.0 中完全删除——朝着永不再输入分号的方向又迈进了一步。
如果您使用 Xcode,可能会获得一个 Fix-it,它会将您的 C 风格 for 循环转换为现代 Swift。在之前的示例中,结果使用了如下范围
for i in 0 ..< 10 {
print(i)
}
但是,Fix-it 的功能是有限的,因此您需要自己完成一些工作。例如,以下两个循环是 Fix-it 目前无法帮助您处理的循环
for var i = 10; i > 0; i-- {
print(i)
}
for var i = 0; i < 10; i += 2 {
print(i)
}
在第一种情况下,您应该使用 (1...10).reverse() 创建反向范围。这与编写 i in 10...1 不同,后者会编译但会在运行时崩溃。在第二种情况下,您应该使用 stride(to:by:) 以 2 为步长计数。因此,为 Swift 2.2 重写这两个循环的正确方法如下所示
for i in (1...10).reverse() {
print(i)
}
for i in 0.stride(to: 10, by: 2) {
print(i)
}
有关更多信息,请参阅 Swift 演化提案 或阅读 swift-evolution-announce 帖子,其中详细说明了理由。
++ 和 -- 已弃用 ++ 和 -- 已弃用 部分的永久链接" href="#-and----are-deprecated">
如果您正在使用 C 风格的 for 循环,那么接下来的更改可能会让您更加惊讶:++ 和 -- 也已弃用,包括作为前缀和后缀运算符。这意味着诸如 for var i = 0; i < 10; i++ 之类的代码不仅包含一个,而是两个弃用,即使在快速发展的 Swift 世界中,这也是非常引人注目的。
此更改意味着以下所有代码现在都已弃用,并将在 Swift 3 中完全停止工作
i++
i--
++i
--i
i = i++
您将需要改用 i += 1 或 i -= 1,Xcode 将为上面的每个示例提供 Fix-it。为了充分公开:i = i++ 的 Fix-it 会给您一个编译器错误,这并不奇怪——i = i++ 到底应该做什么?
此更改没有单一的原因。相反,这是一系列小的原因加起来,最重要的是
- 编写
i++只比编写i += 1稍短一些。 - 如果 Swift 的新手不熟悉
++和--,他们只会加剧学习曲线。 - C 风格的循环——
++和--的常见使用场所——也正在被弃用。 - 使用这些运算符的结果取决于它们是前缀还是后缀使用,这可能会导致混淆。
Swift 演化提案中有一句话简洁地总结了理由:这些未能通过“如果我们还没有这些,我们会将它们添加到 Swift 3 中吗?”的衡量标准。[1]
有关更多信息,请参阅此更改的 Swift 演化提案。
var 参数已弃用 var 参数已弃用 部分的永久链接" href="#var-parameters-are-deprecated">
在 Swift 2.2 之前,如果您想在函数内部修改函数参数,可以将它们声明为 var。例如
func greet(var name: String) {
name = name.uppercaseString
print("Hello, \(name)!")
}
var name = "Taylor"
greet(name)
print("After function, name is \(name)")
虽然这是一个有用的快捷方式,但它确实增加了一些额外的混乱:最终的 print() 语句输出的是 “Taylor” 还是 “TAYLOR”?inout 关键字的存在使这种情况更加令人困惑:在该示例中使用 inout 而不是 var,然后添加一个 & 符号,会生成以下代码
func greet(inout name: String) {
name = name.uppercaseString
print("Hello, \(name)!")
}
var name = "Taylor"
greet(&name)
print("After function, name is \(name)")
运行时,var 示例产生与 inout 示例不同的输出,因为对 var 参数的更改仅在函数内部应用,而对 inout 参数的更改会直接影响原始值。
在 Swift 2.2 中,通过弃用函数参数的 var 关键字(在 Swift 3.0 中删除之前),消除了这种混淆。如果您想复制旧的行为,只需在函数内部创建自己的副本,如下所示
func greet(name: String) {
let uppercaseName = name.uppercaseString
print("Hello, \(uppercaseName)!")
}
有关更多信息,请参阅 Swift 演化提案 或阅读 swift-evolution-announce 帖子,其中详细说明了理由。
重命名的调试标识符
Swift 编译器自动提供一些在调试时有用的符号。以前,这些符号采用“尖叫蛇形命名法”,因此 __FILE__ 将被替换为当前 Swift 文件的名称,__LINE__ 将被替换为行号,依此类推。在 Swift 2.2 中,这些旧的标识符已被弃用,并替换为 #file、#line、#column 和 #function,这引入了“一种约定,其中 # 表示在此处调用编译器替换逻辑”。[2]
这是一个演示旧语法和新语法的示例
func visitCity(name: String, in state: String) {
// old - deprecated!
print("This is on line \(__LINE__) of \(__FUNCTION__)")
print("I'm going to visit \(name) in \(state)")
// new - shiny!
print("This is on line \(#line) of \(#function)")
}
与许多其他更改一样,Xcode 具有 Fix-it,可以正确更新您的代码。
有关更多信息,请参阅 Swift 演化提案 或阅读 swift-evolution-announce 帖子,其中详细说明了理由。
还有更多…
这篇文章涵盖了可能影响大多数开发者的更改,但也引入了其他较小的更改,以及改进的编译器消息和性能增强。点击此处查看官方发布说明,您可以在其中找到关于更改的完整讨论以及 Linux 的安装说明。