让雨燕飞——苹果官方博客Swift Blog翻译

前言

Swift作为Apple推出的语言,具有许多很好的新特性,很值得开发者研究学习。学习一门新语言的第一步就是阅读其官方文档,Apple为Swift专门开放了官方博客Swift Blog用于发布Swift的更新,足见其对于这门语言的重视程度。目前官方博客的内容已更新到Swift 3,笔者将按照时间顺序,对Swift面世至今的所有更新进行翻译,并将持续更新,以供大家共同学习交流,如出现谬误或欠妥之处,还望指正。

Write the code.Change the world.
LetSwiftFly(图片源自网络)

为方便阅读和查找,笔者根据官方的更新次序为本文加上了编号。


print(“欢迎来到Swift Blog”)

2014年7月11日

这个新的博客将由创造这门语言的工程师们为你带来一个从幕后到前台的设计过程,更有最新的消息和提示,以便使你成为一个富有成效的Swift程序员。

开始使用Swift需要下载Xcode 6 beta或更高版本,现在免费提供给所有苹果注册开发者。Swift资源库提供了大量的视频、文档、书籍和示例代码用来帮助你成为世界上第一批Swift专家。当下正是着手编程的最佳时间!


print(“兼容性”)

2014年7月11日

我们在WWDC上被问到最常见的问题之一是:“关于Swift兼容性问题的故事是什么”。这似乎是一个极好的第一个主题。

应用程序兼容性

简单的说,如果你写了一个Swift的应用程序并在今秋iOS 8和OS X Yosemite发布之后提交到App Store,可以信任的是你的应用程序将会运行得很好。事实上,你甚至还可以退回到OS X Mavericks 或 iOS 7应用同一个的应用程序。这可能是由于Xcode嵌入了一个小型的Swift运行库在你的应用程序的bundle中。因为该库是嵌入的,你的应用程序使用一个Swift的一致性版本就可运行在过去、现在以及系统升级后的将来。

二进制兼容和框架

虽然你的应用程序的运行时兼容性得到保证,由于Swift语言本身将持续发展,二进制接口也将随之改变。为了安全起见,你的应用程序的所有组件必须在同一版本的Xcode下构建并且确保他们能一同被Swift编译器编译通过。

这意味着各个框架需要被小心管理。例如,如果你的项目使用框架以嵌入式扩展的方式实现共享代码,你会想将框架、应用程序和扩展建立在一起。使用Swift的基于二进制框架-尤其是第三方框架将会是危险的。随着Swift的发展,那些框架将会不再符合你的应用程序的其余部分。当二进制接口在一两年内稳定时,Swift运行时将成为主机操作系统的一部分,这个限制将不复存在。

源代码的兼容性

现在Swift已经准备好在全新的应用程序或者与已经稳定了的Objective-C代码共存中应用了。为Swift语言,包括改进的语法和强大的新特性,我们有了一些很大的计划。随着Swift的发展,我们将在Xcode中提供工具帮助你迁移源代码。

我们等不及要看你构建的东西了!


print(“Swift语言在 Xcode 6 beta 3中的改变”)

2014年7月15日

Swift语言随着每一个新的Xcode 6测试版本而持续不断地发展,包括新的特性、语法增强以及行为的改进。Xcode 6 Beta 3包含了一些重要的变化,其中的一些我们想强调一下:

  • 数组(Array)已完全重新设计,具有完整的值的语义,以匹配字典(Dictionary)和字符串(String)的行为。现在用let声明的一个数组是完全不可变的,而用var声明的一个数组是完全可变的。

  • 数组和字典的语法“糖”已经改变了。数组用[Int]作为Array<Int>的简写来声明,而不是int []。同样,字典用[Key: Value]作为Dictionary<Key, Value>的简写。

  • 半开区间的运算符被 从.. to ..<修改为相对来说更加清晰的闭区间运算符...

Xcode 6 Beta免费提供给苹果注册开发者并且可以在Xcode的下载页面下载。还可以在Xcode 6 Beta 3完整版本注释中可阅读所有关于这些以及其他变化。


print(“在Swift中构建assert(), 第一部分: Lazy Evaluation”)

2014年7月18日

更新:这次提交更新了反映在Xcode 6 beta 5中的一个改变,@auto_closure 重命名为 @autoclosure,以及 LogicValue 重命名为 BooleanType

设计Swift时我们为去除C语言预处理程序、消除bug以及使代码更易理解做了一个关键的决定。这对开发者来说是一个巨大的胜利,但这同时意味着Swift需要以新的方式来实现一些旧的
特性。这些新的特性大部分使显而易见的(输入模块、条件编译),但或许最有趣的是Swift如何支持宏,比如assert()

当在C语言中构建一个发布版本,assert()宏没有运行时性能的影响,因为它不验证任何参数。在C语言中一种流行的实现方式是这样的:

1
2
3
4
5
6
7
8
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e) \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
((void) ((e) ? ((void)0) : __assert (#e, __FILE__, __LINE__)))
#define __assert(e, file, line) \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
((void)printf ("%s:%u: failed assertion `%s'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n", file, line, e), abort())
#endif

Swift的断言模拟提供了几乎所有C语言中断言的功能性,不使用预处理器,并采用更清晰的方式。让我们深入了解Swift的一些有趣的特性。

参数的Lazy Evaluation

当在Swift中实现assert()时,我们遇到的第一个挑战是没有明显的方法去使一个函数接受一个表达式而不验证它,例如,我们试图使用:

1
2
3
4
5
6
func assert(x : Bool) {
#if !NDEBUG

/*noop*/
#endif
}

即使当断言被禁用时,应用程序将收到评估表达式的性能损失:

1
assert(someExpensiveComputation() != 42)

我们可以解决这一问题的方法之一是通过改变断言的定义来关闭:

1
2
3
4
5
6
7
func assert(predicate : () -> Bool) {
#if !NDEBUG
if !predicate() {
abort()
}
#endif
}

如我们所愿,只有当断言被启用时,这个表达式才对表达式进行验证,但它给我们留下了一个不合适的调用语法:

1
assert({ someExpensiveComputation() != 42 })

我们可以通过使用Swift中的@ autoclosure属性解决这个问题。auto-closure属性可用于函数的参数表明,朴素的表达应该是隐式地包含在一个对应方法的闭包中。例如:

1
2
3
4
5
6
7
func assert(predicate : @autoclosure () -> Bool) {
#if !NDEBUG
if !predicate() {
abort()
}
#endif
}

这可以让你自然地称之,如:

1
assert(someExpensiveComputation() != 42)

auto-closure是一个强大的特性,因为你可以有条件地评估一个表达式,评估它多次,并可以运用绑定表达式以任何方式使用闭包。auto-closure也在Swift的其他地方使用。例如,短路逻辑运算符执行看起来像这样:

1
2
3
func &&(lhs: BooleanType, rhs: @autoclosure () -> BooleanType) -> Bool {
return lhs.boolValue ? rhs().boolValue : false
}

为使右边表达式成为一个 auto-closure,Swift提供了适当子表达式的lazy evaluation(懒评估)。

Auto-Closures

与C语言中的宏一样,Auto-Closures是一个非常强大的特性,必须小心使用,因为没有任何迹象显示在调用方的参数验证受到影响。Auto-Closures是故意限制的,只需要一个空的参数列表,而你不应该在感觉像控制流的情况下使用它们。使用它们时,他们提供了有用的语义,人们会期望(也许是一个“ futures”的接口),但不要使用它们只是为了优化闭包上的braces(支持?)。

这包括Swift里一个特殊的方面实施的断言,但还有更多的。


print(“访问控制(Access Control)”)

2014年7月23日

在Xcode 6 beta 4中,Swift增加了对访问控制的支持。使你能完全控制代码的部分是在单一文件内访问,使你的项目可见或向引入你的框架的人以 API(应用程序编程接口)的形式公开。本次发布中包含的三个访问级别是:

  • private实体只能在它们被定义的源文件中使用。
  • interal实体提供给整个模块,包括定义(例如应用程序或框架目标)。
  • public实体的用于作为API使用,可以通过任何一个导入模块的文件来访问,例如在你的项目中使用的一个框架。

默认情况下,所有实体都有interal访问权限。这允许应用程序开发人员可以在很大程度上忽略访问控制,并且大部分已经写好的Swift代码将会继续工作而不需改变。你的框架代码需要更新来定义public的API,你可以对你的框架提供的公开接口完全控制。

private的访问级别是最严格的,并且可以很容易地隐藏其他源文件的实现细节。通过正确构建你的代码,你可以安全地使用诸如扩展和顶级方法等功能,而不暴露该代码到你的项目的其余部分。

开发人员在他们的项目中使用的构建框架需要将他们的API标志标记为public。虽然不推荐发布和使用第三方的二进制框架(如前一篇博客文章中提到的),但Swift支持源窗体中的框架的构造和分发。

除了允许一个完整的声明的访问规范外,Swift还允许一个属性的获取(get)比它的设置(set)更容易。这里是框架的一部分中的一个示例类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ListItem {

// Public properties.
public var text: String
public var isComplete: Bool

// Readable throughout the module, but only writeable from within this file.
private(set) var UUID: NSUUID

public init(text: String, completed: Bool, UUID: NSUUID) {
self.text = text
self.isComplete = completed
self.UUID = UUID
}

// Usable within the framework target, but not by other targets.
func refreshIdentity() {
self.UUID = NSUUID()
}

public override func isEqual(object: AnyObject?) -> Bool {
if let item = object as? ListItem {
return self.UUID == item.UUID
}
return false
}
}

当Objective-C和Swift混编的时候,由于Objective-C框架中生成的头文件是框架的公共接口部分,而Swift框架中只有声明标志着public的地方为生成的头文件。对于应用程序,生成的头文件同时包含publicinteral的声明。

有关更多信息,Swift编程语言用Cocoa和Objective-C使用Swift的书籍已经更新到包含“访问控制”的部分。在这里阅读完整的Xcode 6 Beta 4版本说明


print(“与C语言指针交互”)

2014年7月28日

Objective-C和C语言的API经常需要使用指针。在Swift中则设计了可与基于指针的Cocoa APIs顺畅运行的数据类型,并且Swift会自动处理几个最常见的用例的指针作为参数。在本次更新中,我们将看看C语言中的指针是如何在Swift的变量(variables)、数组和字符串中使用的。

指针作为参数/输出参数

C语言和Objective-C不支持多个返回值,Cocoa APIs 经常使用指针传递附加数据的方式作为方法的输入或输出,Swift也支持指针参数被当作inout参数,因此同样可以通过使用&语法使var声明的变量作为一个指针传递引用。UIColorgetRed(_:green:blue:alpha:)方法使用4个CGFloat*类型的指针获取颜色的元素,我们可以使用&语法收集这些元素到局部变量:

1
2
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: &a)

另一个常见的情况是Cocoa的NSError语句,许多方法采用NSError **参数在失败的情况下来保存错误数据。例如,我们可以使用 NSFileManagercontentsOfDirectoryAtPath(_:error:)方法列出目录中的内容,保存可能的错误信息到NSError?中:

1
2
3
4
5
6
7
var maybeError: NSError?
if let contents = NSFileManager.defaultManager()
.contentsOfDirectoryAtPath("/usr/bin", error: &maybeError) {
// Work with the directory contents
} else if let error = maybeError {
// Handle the error
}

为了安全,Swift要求变量在被使用&语法传递之前需要初始化,这是因为还无法确定被调用的方法是否会试图从指针在写入它之前读取。

数组参数的指针

指针在C语言中与数组紧紧交织在一起,Swift因使用基于C语言的API之便允许使用Array作为指针变量。正如一个inout参数,不可变数组的值可以作为一个常量指针直接传递,而可变数组可以通过非常量指针参数使用&运算符传递。

例如,我们可以将两个数组ab使用Accelerate框架vdsp_vadd方法将结果写入第三个数组result中:

1
2
3
4
5
6
7
8
9
import Accelerate

let a: [Float] = [1, 2, 3, 4]
let b: [Float] = [0.5, 0.25, 0.125, 0.0625]
var result: [Float] = [0, 0, 0, 0]

vDSP_vadd(a, 1, b, 1, &result, 1, 4)

// result now contains [1.5, 2.25, 3.125, 4.0625]

指针作为字符串参数

C语言以const char*指针作为传递字符串的主要方式,Swift的String可以作为一个const char*指针,它会通过函数指针为空终止字符串的UTF-8编码表示。例如,我们可以直接传递字符串给标准C语言和POSIX库的函数:

1
2
3
4
5
6
7
8
9
10
puts("Hello from libc")
let fd = open("/tmp/scratch.txt", O_WRONLY|O_CREAT, 0o666)

if fd < 0 {
perror("could not open /tmp/scratch.txt")
} else {
let text = "Hello World"
write(fd, text, strlen(text))
close(fd)
}

带有指针参数转换的安全性

Swift难以与C语言的指针方便地进行配合工作,因为它们的普遍性只在于Cocoa,同时提供一定程度的安全。然而,相比你的其他Swift代码,与C指针的相互作用本质上是不安全的,所以必须小心(使用)。特别地:

  • 这些转换在保存调用者指针类型返回值时不能安全地使用。这些转换结果的指针,只保证在调用的持续时间是有效的。即使传递相同的变量、数组或字符串作为多个指针参数,你也可以每次接收不同的指针。这是全局或静态存储变量的一个例外。你可以使用一个全局变量的地址作为一个持续且独特的指针值,例如:作为一个KVO上下文参数。

  • 当传递一个数组或字符串的指针时,不执行边界检查。基于C语言API的数组或字符串不能扩展,所以你必须在将它传递到C语言的API之前确保数组或字符串是正确的大小。

如果你需要使用基于指针的API不遵循这些准则,或者你需要重写接受指针参数的Cocoa方法,那么你可以使用不安全的指针直接在Swift中使用原始内存中工作。我们将在下一个更新中看到更高级的实例。


print(“文件和初始化”)

2014年8月1日

到目前为止,你们中的大部分都已经写了Swift的小应用或者在playground中做了实验。你甚至可能已经遇到过从playground中复制代码到其它文件时发生错误的情况,你一定想知道:“究竟是怎么了?一个playground文件和其它Swift的源文件之间的区别是什么?”本次更新将解释Swift如何处理你项目中的文件,以及如何处事和全局数据。

应用程序中的文件

一个Swift的应用程序是由任意数量的文件组成,每个文件的功能,类和其他声明构成了应用程序。应用程序中的Swift文件是顺序独立(order-independent)的,这意味着你可以在定义一个类型之前使用它,甚至可以在文件下导入一个模型(虽然这不是推荐的Swift风格。)

然而,顶层代码在大多数的Swift源文件中不被允许。为清晰起见,任何一个可执行语句不写在一个函数体、一个类或以其他方式封装被认为是顶级的。

Playgrounds、REPL、以及顶级代码(Top-Level Code)

你可能想知道为什么下面的代码能在playground中完美地工作,这个例子中没有封装任何东西,所以它一定是顶级代码:

1
println("Hello world")

以上的单行程序在没有任何附加代码可以工作,因为playground支持顶级代码的执行。不在playground中的代码是顺序依赖(order-dependent)的,会以自上而下的顺序执行。例如,你不能使用一个没定义的类型。当然,Swift的playground也可以定义一个方法、类以及其它任何Swift的合法代码,但却不需要。

除了playground,顶层代码也可以运行在REPL(Read-Eval-Print-Loop)中或者当启动Swift文件是作为脚本执行。使用Swift的脚本,你可以在终端(Terminal)中使用命令式启动“#!/usr/bin/xcrun swift” 或 “xcrun swift myFile.swift”开始执行Swift文件。

应用程序入口和“main.swift”

你会注意到,我们之前说过顶级代码不允许在你的应用程序的大部分源文件中使用。特例是一个名为“main.swift”的文件可以像playground中的文件那样,但却是由Swift的源文件创建。该文件可以包含顶级代码,并且适用于顺序相关规则。实际上,第一行运行在“main.swift”中的代码是隐式定义的程序主入口。这允许最小的Swift程序可以是一行代码,只要这一行代码是在“main.swift”中。

在Xcode中,Mac模版默认包括“main.swift”文件;而对于iOS应用程序,新的i项目模板默认的是添加@uiapplicationmain到一个正规的Swift文件中。这使编译器为你的iOS应用程序实例化一个main入口,并消除了使用“main.swift”文件的需要。

或者,一般情况下你可以在将项目从Objective-C迁移到Swift过程中使用Objective-C的代码作为执行main的链接。

全局变量

鉴于Swift在哪里开始执行一个应用程序,全局变量如何工作?在下面这行代码中,初始化程序会何时运行?

1
var someGlobal = foo()

在一个单文件程序中,代码是自上而下执行的,类似于方法中的变量。显而易见,复杂应用的情况处理方式不确定,我们考虑了三种不同的选择:

  • 就像C语言一样限制全局变量初始化是简单的常量表达式。
  • 像C++那样允许任何初始化程序如静态构造函数那样在应用程序加载时运行。
  • 类似于Java那样初始化懒加载首次引用时在全局状态下运行初始化程序。

——Swift团队