一、集合类型

Swift 语言提供数组(Array)、集合(Set)和字典(Dictionary)三种基本的集合类型用来存储集合数据

1.1 数组的使用

可以使用构造语法来创建一个由特定数据类型构成的空数组:

  1. var someInts = [Int]()
  2. print("someInts is of type [Int] with \(someInts.count) items.")
  3. // 打印“someInts is of type [Int] with 0 items.”

Swift 中的 Array 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。可以把准备加入新数组的数据项数量(count)和适当类型的初始值(repeating)传入数组构造函数

  1. var threeDoubles = Array(repeating: 0.0, count: 3)
  2. // threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]

字面量创建

  1. var shoppingList: [String] = ["Eggs", "Milk"]
  2. // shoppingList 已经被构造并且拥有两个初始项。

数组添加数据项可以使用下面的方式:

  1. shoppingList.append("Flour")
  2. // shoppingList 现在有3个数据项,似乎有人在摊煎饼
  3. shoppingList += ["Baking Powder"]
  4. // shoppingList 现在有四项了
  5. shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
  6. // shoppingList 现在有七项了
  7. 数组数据替换
  8. shoppingList[4...6] = ["Bananas", "Apples"]
  9. // shoppingList 现在有6项
  10. // 插入
  11. shoppingList.insert("Maple Syrup", at: 0)
  12. // 移除
  13. let mapleSyrup = shoppingList.remove(at: 0)

1.2 Set集合

集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。

  1. var letters = Set<Character>()
  2. print("letters is of type Set<Character> with \(letters.count) items.")
  3. // 打印“letters is of type Set<Character> with 0 items.”
  4. 也可以使用字面量
  5. var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
  6. favoriteGenres 的构造形式可以采用简化的方式代替:
  7. var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

访问一个集合

  1. if let removedGenre = favoriteGenres.remove("Rock") {
  2. print("\(removedGenre)? I'm over it.")
  3. } else {
  4. print("I never much cared for that.")
  5. }
  6. if favoriteGenres.contains("Funk") {
  7. print("I get up on the good foot.")
  8. } else {
  9. print("It's too funky in here.")
  10. }
  11. // 打印“It's

Swift 的 Set 类型没有确定的顺序,为了按照特定顺序来遍历一个集合中的值可以使用 sorted() 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符 < 对元素进行比较的结果来确定。

  1. for genre in favoriteGenres.sorted() {
  2. print("\(genre)")
  3. }
  4. // Classical
  5. // Hip hop
  6. // Jazz

1.3 基本集合操作

下面的插图描述了两个集合 ab,以及通过阴影部分的区域显示集合各种操作的结果。
Swift教程二 - 图1

  • 使用 intersection(_:) 方法根据两个集合的交集创建一个新的集合。
  • 使用 symmetricDifference(_:) 方法根据两个集合不相交的值创建一个新的集合。
  • 使用 union(_:) 方法根据两个集合的所有值创建一个新的集合。
  • 使用 subtracting(_:) 方法根据不在另一个集合中的值创建一个新的集合。
  1. let oddDigits: Set = [1, 3, 5, 7, 9]
  2. let evenDigits: Set = [0, 2, 4, 6, 8]
  3. let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
  4. oddDigits.union(evenDigits).sorted()
  5. // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  6. oddDigits.intersection(evenDigits).sorted()
  7. // []
  8. oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
  9. // [1, 9]
  10. oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
  11. // [1, 2, 9]

1.4 集合成员关系和相等

下面的插图描述了三个集合 abc,以及通过重叠区域表述集合间共享的元素。集合 a 是集合 b父集合,因为 a 包含了 b 中所有的元素。相反的,集合 b 是集合 a子集合,因为属于 b 的元素也被 a 包含。集合 b 和集合 c不相交的,因为它们之间没有共同的元素。
Swift教程二 - 图2

  • 使用“是否相等”运算符(==)来判断两个集合包含的值是否全部相同。
  • 使用 isSubset(of:) 方法来判断一个集合中的所有值是否也被包含在另外一个集合中。
  • 使用 isSuperset(of:) 方法来判断一个集合是否包含另一个集合中所有的值。
  • 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
  • 使用 isDisjoint(with:) 方法来判断两个集合是否不含有相同的值(是否没有交集)。 ```swift let houseAnimals: Set = [“🐶”, “🐱”] let farmAnimals: Set = [“🐮”, “🐔”, “🐑”, “🐶”, “🐱”] let cityAnimals: Set = [“🐦”, “🐭”]

houseAnimals.isSubset(of: farmAnimals) // true farmAnimals.isSuperset(of: houseAnimals) // true farmAnimals.isDisjoint(with: cityAnimals) // true

  1. <a name="HTzQP"></a>
  2. ## 1.5 字典
  3. Swift 的字典使用 `Dictionary<Key, Value>` 定义,其中 `Key` 是一种可以在字典中被用作键的类型,`Value` 是字典中对应于这些键所存储值的数据类型。
  4. ```swift
  5. var namesOfIntegers = [Int: String]()
  6. // namesOfIntegers 是一个空的 [Int: String] 字典
  7. 使用空字典字面量来创建一个空字典,记作 [:]
  8. namesOfIntegers[16] = "sixteen"
  9. // namesOfIntegers 现在包含一个键值对
  10. namesOfIntegers = [:]
  11. // namesOfIntegers 又成为了一个 [Int: String] 类型的空字典
  12. var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

更新字典的数据

  1. if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
  2. print("The old value for DUB was \(oldValue).")
  3. }
  4. // 输出“The old value for DUB was Dublin.”

字典的遍历

  1. for (airportCode, airportName) in airports {
  2. print("\(airportCode): \(airportName)")
  3. }

二、控制流

2.1 for循环

一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 stride(from:to:by:)函数跳过不需要的标记。

  1. let minuteInterval = 5
  2. for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
  3. // 每5分钟渲染一个刻度线(0, 5, 10, 15 ... 45, 50, 55)
  4. }

可以在闭区间使用 stride(from:through:by:) 起到同样作用:

  1. let hours = 12
  2. let hourInterval = 3
  3. for tickMark in stride(from: 3, through: hours, by: hourInterval) {
  4. // 每3小时渲染一个刻度线(3, 6, 9, 12)
  5. }

Repeat-While

  1. repeat {
  2. statements
  3. } while condition

2.2 switch

每一个 case 分支都必须包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:

  1. let anotherCharacter: Character = "a"
  2. switch anotherCharacter {
  3. case "a": // 无效,这个分支下面没有语句
  4. case "A":
  5. print("The letter A")
  6. default:
  7. print("Not the letter A")
  8. }
  9. // 这段代码会报编译错误
  10. 正确写法
  11. let anotherCharacter: Character = "a"
  12. switch anotherCharacter {
  13. case "a", "A":
  14. print("The letter A")
  15. default:
  16. print("Not the letter A")
  17. }
  18. // 输出“The letter A”

2.3 元组

我们可以使用元组在同一个 switch 语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(_)来匹配所有可能的值。

  1. let somePoint = (1, 1)
  2. switch somePoint {
  3. case (0, 0):
  4. print("\(somePoint) is at the origin")
  5. case (_, 0):
  6. print("\(somePoint) is on the x-axis")
  7. case (0, _):
  8. print("\(somePoint) is on the y-axis")
  9. case (-2...2, -2...2):
  10. print("\(somePoint) is inside the box")
  11. default:
  12. print("\(somePoint) is outside of the box")
  13. }
  14. // 输出“(1, 1) is inside the box”

2.4控制转移语句

2.4.1 Continue

continue 语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。

2.4.2 Break

break 语句会立刻结束整个控制流的执行。break 可以在 switch 或循环语句中使用,用来提前结束 switch 或循环语句。

2.4.3 贯穿(Fallthrough)

在 Swift 里,switch 语句不会从上一个 case 分支跳转到下一个 case 分支中。

  1. let integerToDescribe = 5
  2. var description = "The number \(integerToDescribe) is"
  3. switch integerToDescribe {
  4. case 2, 3, 5, 7, 11, 13, 17, 19:
  5. description += " a prime number, and also"
  6. fallthrough
  7. default:
  8. description += " an integer."
  9. }
  10. print(description)
  11. // 输出“The number 5 is a prime number, and also an integer.”

添加标签

  1. label name: while condition {
  2. statements
  3. }

2.5 提前退出

  1. if 语句一样,guard 的执行取决于一个表达式的布尔值。我们可以使用 guard 语句来要求条件必须为真时,以执行 guard 语句后的代码。不同于 if 语句,一个 guard 语句总是有一个 else 从句,如果条件不为真则执行 else 从句中的代码。

2.6 检测 API 可用性

Swift 内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的 API。
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。

  1. if #available(iOS 10, macOS 10.12, *) {
  2. // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
  3. } else {
  4. // 使用先前版本的 iOS 和 macOS 的 API
  5. }

三、函数

3.1 函数定义:

  1. func greet(person: String) -> String {
  2. let greeting = "Hello, " + person + "!"
  3. return greeting
  4. }
  5. print(greet(person: "Anna"))
  6. // 打印“Hello, Anna!”

3.2多重返回值函数

你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
下例中定义了一个名为 minMax(array:) 的函数,作用是在一个 Int 类型的数组中找出最小值与最大值。

  1. func minMax(array: [Int]) -> (min: Int, max: Int) {
  2. var currentMin = array[0]
  3. var currentMax = array[0]
  4. for value in array[1..<array.count] {
  5. if value < currentMin {
  6. currentMin = value
  7. } else if value > currentMax {
  8. currentMax = value
  9. }
  10. }
  11. return (currentMin, currentMax)
  12. }

minMax(array:) 函数返回一个包含两个 Int 值的元组,这些值被标记为 minmax ,以便查询函数的返回值时可以通过名字访问它们。

  1. let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
  2. print("min is \(bounds.min) and max is \(bounds.max)")
  3. // 打印“min is -6 and max is 109”

3.3 可选元组返回类型

你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 (Int, Int)?(String, Int, Bool)?

注意 可选元组类型如 (Int, Int)? 与元组包含可选类型如 (Int?, Int?) 是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。

  1. func minMax(array: [Int]) -> (min: Int, max: Int)? {
  2. if array.isEmpty { return nil }
  3. var currentMin = array[0]
  4. var currentMax = array[0]
  5. for value in array[1..<array.count] {
  6. if value < currentMin {
  7. currentMin = value
  8. } else if value > currentMax {
  9. currentMax = value
  10. }
  11. }
  12. return (currentMin, currentMax)
  13. }
  14. if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
  15. print("min is \(bounds.min) and max is \(bounds.max)")
  16. }
  17. // 打印“min is -6 and max is 109”

3.4 隐式返回的函数

如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。举个例子,以下的函数有着同样的作用

  1. func greeting(for person: String) -> String {
  2. "Hello, " + person + "!"
  3. }
  4. print(greeting(for: "Dave"))
  5. // 打印 "Hello, Dave!"

每个函数参数都有一个参数标签(argument label)以及一个参数名称(parameter name)

  1. func someFunction(firstParameterName: Int, secondParameterName: Int) {
  2. // 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
  3. }
  4. someFunction(firstParameterName: 1, secondParameterName: 2)

这个版本的 greet(person:) 函数,接收一个人的名字和他的家乡,并且返回一句问候:

  1. func greet(person: String, from hometown: String) -> String {
  2. return "Hello \(person)! Glad you could visit from \(hometown)."
  3. }
  4. print(greet(person: "Bill", from: "Cupertino"))
  5. // 打印“Hello Bill! Glad you could visit from Cupertino.”

3.5 可变参数

一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(...)的方式来定义可变参数。

3.6 嵌套函数

把函数定义在别的函数体中,称作 嵌套函数(nested functions)
默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。

  1. func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  2. func stepForward(input: Int) -> Int { return input + 1 }
  3. func stepBackward(input: Int) -> Int { return input - 1 }
  4. return backward ? stepBackward : stepForward
  5. }
  6. var currentValue = -4
  7. let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
  8. // moveNearerToZero now refers to the nested stepForward() function
  9. while currentValue != 0 {
  10. print("\(currentValue)... ")
  11. currentValue = moveNearerToZero(currentValue)
  12. }
  13. print("zero!")
  14. // -4...
  15. // -3...
  16. // -2...
  17. // -1...
  18. // zero!

四、闭包


闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。

函数 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:

  • 利用上下文推断参数和返回值类型
  • 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
  • 参数名称缩写
  • 尾随闭包语法

闭包表达式语法

闭包表达式语法有如下的一般形式:

  1. { (parameters) -> return type in
  2. statements
  3. }
  4. reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
  5. return s1 > s2
  6. })

根据上下文推断类型

因为排序闭包函数是作为 sorted(by:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。sorted(by:) 方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。这意味着 (String, String)Bool 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:

  1. reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

参数名称缩写

Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0$1$2 来顺序调用闭包的参数,以此类推。

如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

  1. reversedNames = names.sorted(by: { $0 > $1 } )