A Swift Tour Swift 快速入门指南
了解 Swift 的功能和语法特点。
Tradition suggests that the first program in a new language should print the words “Hello, world!” on the screen. In Swift, this can be done in a single line:
传统上,新语言的第一个程序通常会在屏幕上显示“Hello, world!”这句话。在 Swift 里,这只需一行代码即可实现:
print("Hello, world!")
// Prints "Hello, world!"
This syntax should look familiar if you know another language — in Swift, this line of code is a complete program. You don’t need to import a separate library for functionality like outputting text or handling strings. Code written at global scope is used as the entry point for the program, so you don’t need a main() function. You also don’t need to write semicolons at the end of every statement.
如果你熟悉其他编程语言,这种语法应该很容易理解——在 Swift 中,这一行代码就构成了一个完整的程序。你无需导入额外的库来实现输出文本或处理字符串等功能。全局作用域中的代码即作为程序的入口点,因此不需要写 main() 函数,也不必在每条语句末尾加分号。
This tour gives you enough information to start writing code in Swift by showing you how to accomplish a variety of programming tasks. Don’t worry if you don’t understand something — everything introduced in this tour is explained in detail in the rest of this book.
本导览将向您展示如何完成各种编程任务,帮助您掌握足够的信息,开始用 Swift 编写代码。即使有不明白的地方也不用担心——本导览中介绍的内容都会在本书后续章节中详细讲解。
Simple Values 简单数据类型
Use let to make a constant and var to make a variable. The value of a constant doesn’t need to be known at compile time, but you must assign it a value exactly once. This means you can use constants to name a value that you determine once but use in many places.
使用 let 来声明常量,使用 var 来声明变量。常量的值不必在编译时就确定,但必须且只能赋值一次。这样,你就可以用常量来表示一个确定后多次使用的值。
var myVariable = 42
myVariable = 50
let myConstant = 42
A constant or variable must have the same type as the value you want to assign to it. However, you don’t always have to write the type explicitly. Providing a value when you create a constant or variable lets the compiler infer its type. In the example above, the compiler infers that my is an integer because its initial value is an integer.
常量或变量的类型必须与赋给它的值类型相同。不过,您不必总是显式地写出类型。创建常量或变量时,如果提供了初始值,编译器会根据该值自动推断类型。在上面的例子中,编译器推断 my 是整数,因为它的初始值是整数。
If the initial value doesn’t provide enough information (or if there isn’t an initial value), specify the type by writing it after the variable, separated by a colon.
如果初始值信息不足(或没有初始值),请在变量后用冒号写出类型以明确指定。
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
Values are never implicitly converted to another type. If you need to convert a value to a different type, explicitly make an instance of the desired type.
值不会被自动转换成其他类型。如果需要将值转换为另一种类型,必须明确地创建该类型的实例。
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
There’s an even simpler way to include values in strings: Write the value in parentheses, and write a backslash (\) before the parentheses. For example:
有一种更简单的方式将值插入字符串:把值写在括号里,并在括号前加一个反斜杠( \ )。例如:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
Use three double quotation marks (""") for strings that take up multiple lines. Indentation at the start of each quoted line is removed, as long as it matches the indentation of the closing quotation marks. For example:
使用三个双引号( """ )来表示多行字符串。只要每行开头的缩进与结束引号的缩进一致,这些缩进就会被自动去除。例如:
let quotation = """
Even though there's whitespace to the left,
the actual lines aren't indented.
Except for this line.
Double quotes (") can appear without being escaped.
I still have \(apples + oranges) pieces of fruit.
"""
Create arrays and dictionaries using brackets ([]), and access their elements by writing the index or key in brackets. A comma is allowed after the last element.
使用括号( [] )创建数组和字典,通过在括号中写入索引或键来访问元素。最后一个元素后可以加逗号。
var fruits = ["strawberries", "limes", "tangerines"]
fruits[1] = "grapes"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
Arrays automatically grow as you add elements.
数组会随着添加元素自动扩展。
fruits.append("blueberries")
print(fruits)
// Prints "["strawberries", "grapes", "tangerines", "blueberries"]"
You also use brackets to write an empty array or dictionary. For an array, write [], and for a dictionary, write [:].
你也可以用括号来表示空数组或字典。空数组写成 [] ,空字典写成 [:] 。
fruits = []
occupations = [:]
If you’re assigning an empty array or dictionary to a new variable, or another place where there isn’t any type information, you need to specify the type.
如果你把一个空数组或字典赋值给新变量,或者赋值给没有类型信息的地方,就需要明确指定类型。
let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]
Control Flow 程序流程控制
Use if and switch to make conditionals, and use for-in, while, and repeat-while to make loops. Parentheses around the condition or loop variable are optional. Braces around the body are required.
使用 if 和 switch 来编写条件语句,使用 for - in 、 while 以及 repeat - while 来编写循环。条件或循环变量周围的括号可有可无,但代码块必须用大括号包裹。
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
// Prints "11"
In an if statement, the conditional must be a Boolean expression — this means that code such as if score { ... } is an error, not an implicit comparison to zero.
在 if 语句中,条件必须是布尔表达式——这表示像 if score { ... } 这样的代码是错误的,而不是默认与零进行比较。
You can write if or switch after the equal sign (=) of an assignment or after return, to choose a value based on the condition.
你可以在赋值语句的等号( = )后面,或者在 return 之后,写 if 或 switch ,以根据条件选择对应的值。
let scoreDecoration = if teamScore > 10 {
"🎉"
} else {
""
}
print("Score:", teamScore, scoreDecoration)
// Prints "Score: 11 🎉"
You can use if and let together to work with values that might be missing. These values are represented as optionals. An optional value either contains a value or contains nil to indicate that a value is missing. Write a question mark (?) after the type of a value to mark the value as optional.
你可以将 if 和 let 结合使用,以处理可能缺失的值。这些值被表示为可选类型。可选值要么包含一个具体的值,要么包含 nil ,表示值缺失。只需在类型后加一个问号( ? ),即可将该值标记为可选。
var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
If the optional value is nil, the conditional is false and the code in braces is skipped. Otherwise, the optional value is unwrapped and assigned to the constant after let, which makes the unwrapped value available inside the block of code.
如果可选值为 nil ,条件为 false ,则大括号内的代码会被跳过。否则,可选值会被解包并赋值给 let 后的常量,从而使解包后的值可以在代码块中使用。
Another way to handle optional values is to provide a default value using the ?? operator. If the optional value is missing, the default value is used instead.
处理可选值的另一种方法是使用 ?? 操作符来提供默认值,当可选值不存在时,会使用该默认值。
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
You can use a shorter spelling to unwrap a value, using the same name for that unwrapped value.
你可以用更简洁的写法来解包一个值,并使用相同的名称来表示这个解包后的值。
if let nickname {
print("Hey, \(nickname)")
}
// Doesn't print anything, because nickname is nil.
Switches support any kind of data and a wide variety of comparison operations — they aren’t limited to integers and tests for equality.
Switch 语句支持各种数据类型和多种比较操作,不仅限于整数或相等判断。
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
// Prints "Is it a spicy red pepper?"
Notice how let can be used in a pattern to assign the value that matched the pattern to a constant.
注意如何在模式中使用 let ,将匹配到的值赋给一个常量。
After executing the code inside the switch case that matched, the program exits from the switch statement. Execution doesn’t continue to the next case, so you don’t need to explicitly break out of the switch at the end of each case’s code.
当执行完匹配的 switch case 中的代码后,程序会自动退出 switch 语句,不会继续执行下一个 case,因此无需在每个 case 代码末尾显式使用 break。
You use for-in to iterate over items in a dictionary by providing a pair of names to use for each key-value pair. Dictionaries are an unordered collection, so their keys and values are iterated over in an arbitrary order.
你可以使用 for - in 来遍历字典中的元素,为每个键值对指定一对名称。由于字典是无序集合,键和值的遍历顺序是随机的。
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
// Prints "25"
Use while to repeat a block of code until a condition changes. The condition of a loop can be at the end instead, ensuring that the loop is run at least once.
使用 while 可以重复执行一段代码块,直到条件发生变化。将循环条件放在末尾,则可以保证循环至少执行一次。
var n = 2
while n < 100 {
n *= 2
}
print(n)
// Prints "128"
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
// Prints "128"
You can keep an index in a loop by using ..< to make a range of indexes.
你可以在循环中使用 ..< 来生成一系列索引,从而跟踪索引位置。
var total = 0
for i in 0..<4 {
total += i
}
print(total)
// Prints "6"
Use ..< to make a range that omits its upper value, and use ... to make a range that includes both values.
使用 ..< 可以创建一个不包含上限的区间,使用 ... 则可以创建一个包含起始值和结束值的区间。
Functions and Closures 函数与闭包
Use func to declare a function. Call a function by following its name with a list of arguments in parentheses. Use -> to separate the parameter names and types from the function’s return type.
使用 func 来声明函数。调用函数时,在函数名后加上括号,括号内写入参数列表。使用 -> 将参数名和类型与函数的返回类型分开。
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
By default, functions use their parameter names as labels for their arguments. Write a custom argument label before the parameter name, or write _ to use no argument label.
默认情况下,函数会使用参数名作为参数标签。你可以在参数名前写自定义的标签,或者写 _ 来表示不使用参数标签。
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
Use a tuple to make a compound value — for example, to return multiple values from a function. The elements of a tuple can be referred to either by name or by number.
使用元组来表示复合值——例如,可以用它从函数返回多个值。元组中的元素既可以通过名称,也可以通过索引来访问。
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// Prints "120"
print(statistics.2)
// Prints "120"
Functions can be nested. Nested functions have access to variables that were declared in the outer function. You can use nested functions to organize the code in a function that’s long or complex.
函数可以嵌套,嵌套函数能够访问外部函数中声明的变量。通过使用嵌套函数,可以更好地组织较长或复杂函数中的代码。
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
Functions are a first-class type. This means that a function can return another function as its value.
函数是一等公民,这意味着函数可以作为返回值返回另一个函数。
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
A function can take another function as one of its arguments.
一个函数可以把另一个函数作为参数传入。
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
Functions are actually a special case of closures: blocks of code that can be called later. The code in a closure has access to things like variables and functions that were available in the scope where the closure was created, even if the closure is in a different scope when it’s executed — you saw an example of this already with nested functions. You can write a closure without a name by surrounding code with braces ({}). Use in to separate the arguments and return type from the body.
函数其实是闭包的一种特殊形式:闭包是可以稍后调用的代码块。闭包中的代码能够访问创建闭包时所在作用域中的变量和函数,即使闭包在执行时处于不同的作用域——你之前已经通过嵌套函数见过这个例子。你可以用大括号( {} )包裹代码来编写无名闭包,使用 in 将参数和返回值类型与闭包主体分开。
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
You have several options for writing closures more concisely. When a closure’s type is already known, such as the callback for a delegate, you can omit the type of its parameters, its return type, or both. Single statement closures implicitly return the value of their only statement.
编写闭包时,你有多种简化写法可选。当闭包的类型已知(例如代理的回调函数),你可以省略参数类型、返回类型,或者两者都省略。单语句闭包会自动返回该语句的结果。
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"
You can refer to parameters by number instead of by name — this approach is especially useful in very short closures. A closure passed as the last argument to a function can appear immediately after the parentheses. When a closure is the only argument to a function, you can omit the parentheses entirely.
你可以用数字来引用参数,而不是用名称——这种方式在非常短的闭包中尤其实用。作为函数最后一个参数传入的闭包,可以直接写在括号后面。如果闭包是函数唯一的参数,甚至可以省略括号。
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"
Objects and Classes 对象与类
Use class followed by the class’s name to create a class. A property declaration in a class is written the same way as a constant or variable declaration, except that it’s in the context of a class. Likewise, method and function declarations are written the same way.
使用 class 加上类名来创建一个类。类中的属性声明方式与常量或变量声明相同,只不过是在类的环境中。同样,方法和函数的声明方式也一样。
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
Create an instance of a class by putting parentheses after the class name. Use dot syntax to access the properties and methods of the instance.
在类名后加上括号即可创建该类的实例。然后,可以通过点语法访问该实例的属性和方法。
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
This version of the Shape class is missing something important: an initializer to set up the class when an instance is created. Use init to create one.
这个版本的 Shape 类缺少一个关键部分:用于在实例创建时初始化类的构造器。请使用 init 来创建它。
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
Notice how self is used to distinguish the name property from the name argument to the initializer. The arguments to the initializer are passed like a function call when you create an instance of the class. Every property needs a value assigned — either in its declaration (as with number) or in the initializer (as with name).
注意 self 用于区分 name 属性和初始化器的 name 参数。在创建类实例时,初始化器的参数像函数调用一样传入。每个属性都必须被赋值——要么在声明时(如 number ),要么在初始化器中(如 name )。
Use deinit to create a deinitializer if you need to perform some cleanup before the object is deallocated.
如果需要在对象被销毁前进行清理操作,可以使用 deinit 来定义析构器。
Subclasses include their superclass name after their class name, separated by a colon. There’s no requirement for classes to subclass any standard root class, so you can include or omit a superclass as needed.
子类的类名后面会加上父类名,中间用冒号隔开。类不必一定继承某个标准根类,可以根据需要选择是否包含父类。
Methods on a subclass that override the superclass’s implementation are marked with override — overriding a method by accident, without override, is detected by the compiler as an error. The compiler also detects methods with override that don’t actually override any method in the superclass.
子类中重写父类方法的实现时,会用 override 进行标记——如果无意中重写了方法却没有使用 override ,编译器会将其视为错误。同时,编译器也会检测那些带有 override 标记但实际上并未重写父类任何方法的方法。
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
In addition to simple properties that are stored, properties can have a getter and a setter.
除了存储简单属性外,属性还可以定义 getter 和 setter 方法。
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
// Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003"
In the setter for perimeter, the new value has the implicit name new. You can provide an explicit name in parentheses after set.
在 perimeter 的 setter 中,新值的隐式名称是 new 。你可以在 set 后的括号中指定一个显式名称。
Notice that the initializer for the Equilateral class has three different steps:
请注意, Equilateral 类的初始化器包含三个不同的步骤:
Setting the value of properties that the subclass declares.
为子类声明的属性赋值。Calling the superclass’s initializer.
调用父类的初始化方法。Changing the value of properties defined by the superclass. Any additional setup work that uses methods, getters, or setters can also be done at this point.
修改父类定义的属性值。此时也可以完成任何使用方法、getter 或 setter 的额外初始化工作。
If you don’t need to compute the property but still need to provide code that’s run before and after setting a new value, use will and did. The code you provide is run any time the value changes outside of an initializer. For example, the class below ensures that the side length of its triangle is always the same as the side length of its square.
如果你不需要计算属性,但仍想在设置新值前后执行一些代码,可以使用 will 和 did 。这些代码会在值在初始化器之外发生变化时被调用。比如,下面的类确保三角形的边长始终与正方形的边长保持一致。
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"
When working with optional values, you can write ? before operations like methods, properties, and subscripting. If the value before the ? is nil, everything after the ? is ignored and the value of the whole expression is nil. Otherwise, the optional value is unwrapped, and everything after the ? acts on the unwrapped value. In both cases, the value of the whole expression is an optional value.
在使用可选值时,可以在方法、属性和下标等操作前加上 ? 。如果 ? 前的值是 nil ,那么 ? 之后的所有内容都会被忽略,整个表达式的结果是 nil 。否则,可选值会被解包, ? 之后的操作会作用于解包后的值。无论哪种情况,整个表达式的结果都是一个可选值。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
Enumerations and Structures
枚举与结构体介绍
Use enum to create an enumeration. Like classes and all other named types, enumerations can have methods associated with them.
使用 enum 来创建枚举。和类以及其他所有命名类型一样,枚举也可以拥有自己的方法。
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
By default, Swift assigns the raw values starting at zero and incrementing by one each time, but you can change this behavior by explicitly specifying values. In the example above, Ace is explicitly given a raw value of 1, and the rest of the raw values are assigned in order. You can also use strings or floating-point numbers as the raw type of an enumeration. Use the raw property to access the raw value of an enumeration case.
默认情况下,Swift 会从零开始为枚举成员分配原始值,每次递增一,但您也可以通过显式指定值来改变这种行为。在上面的示例中, Ace 被明确赋值为 1 ,其余成员的原始值则按顺序自动分配。枚举的原始类型还可以是字符串或浮点数。可以通过 raw 属性来访问枚举成员的原始值。
Use the init?(raw initializer to make an instance of an enumeration from a raw value. It returns either the enumeration case matching the raw value or nil if there’s no matching Rank.
使用 init?(raw 初始化器可以通过原始值创建枚举实例。它会返回与该原始值对应的枚举成员,如果没有匹配的 Rank ,则返回 nil 。
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
The case values of an enumeration are actual values, not just another way of writing their raw values. In fact, in cases where there isn’t a meaningful raw value, you don’t have to provide one.
枚举的 case 值是真实的值,而不仅仅是原始值的另一种表示方式。实际上,如果没有合适的原始值,你可以不必提供。
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
Notice the two ways that the hearts case of the enumeration is referred to above: When assigning a value to the hearts constant, the enumeration case Suit is referred to by its full name because the constant doesn’t have an explicit type specified. Inside the switch, the enumeration case is referred to by the abbreviated form .hearts because the value of self is already known to be a suit. You can use the abbreviated form anytime the value’s type is already known.
注意上面提到的枚举中 hearts 情况的两种引用方式:当给 hearts 常量赋值时,由于该常量没有显式指定类型,枚举情况 Suit 使用完整名称引用。而在 switch 语句内部,由于 self 的值已经确定为一个花色,枚举情况则使用简写形式 .hearts 。只要值的类型已知,就可以随时使用简写形式。
If an enumeration has raw values, those values are determined as part of the declaration, which means every instance of a particular enumeration case always has the same raw value. Another choice for enumeration cases is to have values associated with the case — these values are determined when you make the instance, and they can be different for each instance of an enumeration case. You can think of the associated values as behaving like stored properties of the enumeration case instance. For example, consider the case of requesting the sunrise and sunset times from a server. The server either responds with the requested information, or it responds with a description of what went wrong.
如果一个枚举有原始值,这些值是在声明时确定的,这意味着某个枚举成员的每个实例总是拥有相同的原始值。另一种枚举成员的选择是为其关联值——这些值在创建实例时确定,并且每个实例的关联值可以不同。你可以把关联值看作是枚举成员实例的存储属性。举个例子,假设你从服务器请求日出和日落时间,服务器要么返回请求的信息,要么返回一个描述错误原因的说明。
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."
Notice how the sunrise and sunset times are extracted from the Server value as part of matching the value against the switch cases.
注意在将值与 switch 语句的各个分支匹配时,如何从 Server 值中提取日出和日落时间。
Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they’re passed around in your code, but classes are passed by reference.
使用 struct 来创建结构体。结构体支持许多与类相同的功能,包括方法和初始化器。结构体和类最重要的区别之一是,结构体在代码中传递时会被复制,而类则是通过引用传递。
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
Concurrency 并发编程
Use async to mark a function that runs asynchronously.
使用 async 标记一个异步执行的函数。
func fetchUserID(from server: String) async -> Int {
if server == "primary" {
return 97
}
return 501
}
You mark a call to an asynchronous function by writing await in front of it.
你可以在异步函数调用前加上 await 来标记该调用。
func fetchUsername(from server: String) async -> String {
let userID = await fetchUserID(from: server)
if userID == 501 {
return "John Appleseed"
}
return "Guest"
}
Use async let to call an asynchronous function, letting it run in parallel with other asynchronous code. When you use the value it returns, write await.
使用 async let 调用异步函数,使其能与其他异步代码同时运行。当你需要使用它返回的值时,写 await 。
func connectUser(to server: String) async {
async let userID = fetchUserID(from: server)
async let username = fetchUsername(from: server)
let greeting = await "Hello \(username), user ID \(userID)"
print(greeting)
}
Use Task to call asynchronous functions from synchronous code, without waiting for them to return.
使用 Task 可以在同步代码中调用异步函数,无需等待其返回结果。
Task {
await connectUser(to: "primary")
}
// Prints "Hello Guest, user ID 97"
Use task groups to structure concurrent code.
使用任务组来组织并发代码。
let userIDs = await withTaskGroup(of: Int.self) { group in
for server in ["primary", "secondary", "development"] {
group.addTask {
return await fetchUserID(from: server)
}
}
var results: [Int] = []
for await result in group {
results.append(result)
}
return results
}
Actors are similar to classes, except they ensure that different asynchronous functions can safely interact with an instance of the same actor at the same time.
Actor 类似于普通类,但它们保证了不同的异步函数能够安全地同时访问同一个 actor 实例。
actor ServerConnection {
var server: String = "primary"
private var activeUsers: [Int] = []
func connect() async -> Int {
let userID = await fetchUserID(from: server)
// ... communicate with server ...
activeUsers.append(userID)
return userID
}
}
When you call a method on an actor or access one of its properties, you mark that code with await to indicate that it might have to wait for other code that’s already running on the actor to finish.
当你调用 actor 的方法或访问其属性时,需要用 await 标记这段代码,表示它可能需要等待该 actor 上正在运行的其他代码执行完毕。
let server = ServerConnection()
let userID = await server.connect()
Protocols and Extensions
协议与扩展
Use protocol to declare a protocol.
使用 protocol 来定义一个协议。
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
Classes, enumerations, and structures can all adopt protocols.
类、枚举和结构体都可以遵循协议。
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
Notice the use of the mutating keyword in the declaration of Simple to mark a method that modifies the structure. The declaration of Simple doesn’t need any of its methods marked as mutating because methods on a class can always modify the class.
注意在声明 Simple 时使用 mutating 关键字来标记会修改结构的方法。而 Simple 的声明中不需要将任何方法标记为 mutating,因为类的方法本身就可以修改类的内容。
Use extension to add functionality to an existing type, such as new methods and computed properties. You can use an extension to add protocol conformance to a type that’s declared elsewhere, or even to a type that you imported from a library or framework.
使用 extension 可以为现有类型添加功能,比如新增方法和计算属性。您还可以通过扩展为在其他地方声明的类型,或者从库或框架导入的类型,添加协议的遵循。
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// Prints "The number 7"
You can use a protocol name just like any other named type — for example, to create a collection of objects that have different types but that all conform to a single protocol. When you work with values whose type is a boxed protocol type, methods outside the protocol definition aren’t available.
你可以像使用其他命名类型一样使用协议名称——例如,创建一个包含不同类型但都遵循同一协议的对象集合。当你操作类型为封装协议的值时,协议外定义的方法将无法使用。
let protocolValue: any ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class. Now 100% adjusted."
// print(protocolValue.anotherProperty) // Uncomment to see the error
Even though the variable protocol has a runtime type of Simple, the compiler treats it as the given type of Example. This means that you can’t accidentally access methods or properties that the class implements in addition to its protocol conformance.
尽管变量 protocol 在运行时的类型是 Simple ,但编译器会将其当作指定的类型 Example 来处理。这意味着你无法意外访问该类除了遵循协议之外所实现的方法或属性。
Error Handling 错误处理机制
You represent errors using any type that adopts the Error protocol.
你可以使用任何遵循 Error 协议的类型来表示错误。
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
Use throw to throw an error and throws to mark a function that can throw an error. If you throw an error in a function, the function returns immediately and the code that called the function handles the error.
使用 throw 来抛出错误,使用 throws 来标记一个可能抛出错误的函数。如果函数中抛出了错误,函数会立即返回,调用该函数的代码负责处理该错误。
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
There are several ways to handle errors. One way is to use do-catch. Inside the do block, you mark code that can throw an error by writing try in front of it. Inside the catch block, the error is automatically given the name error unless you give it a different name.
处理错误有多种方法,其中一种是使用 do - catch 。在 do 代码块中,通过在可能抛出错误的代码前加上 try 来标记。在 catch 代码块中,错误会自动命名为 error ,除非你另行指定名称。
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
// Prints "Job sent"
You can provide multiple catch blocks that handle specific errors. You write a pattern after catch just as you do after case in a switch.
你可以提供多个 catch 块来处理特定的错误。你在 catch 后面写的模式,就像在 switch 语句中的 case 后面写的一样。
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
// Prints "Job sent"
Another way to handle errors is to use try? to convert the result to an optional. If the function throws an error, the specific error is discarded and the result is nil. Otherwise, the result is an optional containing the value that the function returned.
处理错误的另一种方法是使用 try? 将结果转换为可选类型。如果函数抛出错误,具体错误会被忽略,结果为 nil 。否则,结果是一个包含函数返回值的可选值。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
Use defer to write a block of code that’s executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error. You can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.
使用 defer 编写一段代码块,这段代码会在函数中所有其他代码执行完毕后、函数返回之前执行,无论函数是否抛出错误,该代码都会执行。你可以使用 defer 将初始化和清理代码放在一起编写,虽然它们实际执行的时间不同。
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
if fridgeContains("banana") {
print("Found a banana")
}
print(fridgeIsOpen)
// Prints "false"
Generics 泛型(通用类型)
Write a name inside angle brackets to make a generic function or type.
在尖括号中写入名称,用于定义泛型函数或类型。
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result: [Item] = []
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
You can make generic forms of functions and methods, as well as classes, enumerations, and structures.
你可以为函数、方法,以及类、枚举和结构体创建通用的形式。
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
Use where right before the body to specify a list of requirements — for example, to require the type to implement a protocol, to require two types to be the same, or to require a class to have a particular superclass.
在主体之前使用 where 来指定一组要求——例如,要求某个类型实现某个协议,要求两个类型相同,或者要求某个类继承自特定的父类。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
Writing <T: Equatable> is the same as writing <T> ... where T: Equatable.
写 <T: Equatable> 和写 <T> ... where T: Equatable 是相同的。