Swift 从入门到精通-第一篇

0 阅读8分钟

第1章:初识 Swift

1.1 Swift 是什么?

Swift 是 Apple 在 2014 年推出的编程语言,用来开发 iOS、macOS、watchOS 和 tvOS 应用。它结合了 C 和 Objective-C 的优点,同时去掉了复杂的语法包袱。

Swift 的特点:

  • 安全:类型安全、内存安全,很多错误在编译期就能发现
  • 快速:性能接近 C++,比 Python 快几十倍
  • 现代:支持函数式编程、面向协议编程
  • 易读:语法接近自然语言,代码像英语一样好读

1.2 你的第一个 Swift 程序

打开 Xcode,新建一个 Playground(游乐场),这是学习 Swift 最好的方式——写一行代码,立刻看到结果。

// 这是注释,Swift 不会执行它
print("Hello, World!")  // 输出:Hello, World!

为什么用 Playground?

  • 即时反馈:改一行代码,右边立刻显示结果
  • 无需编译:不像正式项目需要等很久
  • 完美实验:可以随意尝试,不会搞坏任何东西

第2章:变量和常量 - 数据的容器

2.1 为什么要区分变量和常量?

想象你在做一个计算器 App:

  • 变量 (var) :计算结果会变,比如当前显示的数字
  • 常量 (let) :圆周率 π 永远不会变

这个区分很重要!Swift 强制你明确哪些值会改变,这能避免很多 bug。

2.2 定义变量和常量

// 常量:用 let,值一旦设定就不能改
let pi = 3.14159
// pi = 3.14  // ❌ 错误!不能修改常量

// 变量:用 var,可以随时修改
var score = 100
score = 95    // ✅ 可以修改
score = 98    // ✅ 再改一次

黄金法则:默认用 let,只有真正需要改变时才用 var。这让代码更安全,也更容易理解。

2.3 类型推断和显式声明

Swift 很聪明,能猜出你存的是什么类型:

let name = "Alice"      // Swift 推断为 String
let age = 25            // Swift 推断为 Int
let price = 19.99       // Swift 推断为 Double

但有时你需要显式告诉 Swift 类型:

let red: UInt8 = 255          // 明确要用 8 位无符号整数

什么时候需要显式声明?

  • 初始值不够明确(比如空数组)
  • 需要特定精度(Float 比 Double 省内存)
  • 代码可读性考虑

2.4 基本数据类型

整数 (Int)

let positive: Int = 42
let negative: Int = -17
let zero: Int = 0

// 不同大小的整数(少用,除非有特定需求)
let small: Int8 = 127           // -128127
let medium: Int16 = 32767       // -3276832767
let big: Int64 = 9223372036854775807

选择建议:99% 的情况直接用 Int,Swift 会自动选择 32 位或 64 位。

浮点数 (Double / Float)

let precise: Double = 3.14159265358979    // 15 位小数精度
let lessPrecise: Float = 3.14159          // 6 位小数精度,省内存

选择建议:默认用 Double,除非你在做图形处理或嵌入式开发需要省内存。

布尔值 (Bool)

let hasError = false

// 用在条件判断中
if isLoading {
    print("请稍等...")
}

字符串 (String)

// 字符串用双引号
let greeting = "Hello, Swift!"

// 空字符串
var emptyString = ""
var anotherEmpty = String()

// 检查是否为空
if emptyString.isEmpty {
    print("字符串是空的")
}

2.5 字符串插值 - 把变量放进字符串

这是 Swift 最方便的特性之一:

let name = "小明"
let age = 20
let message = "我叫(name),今年(age)岁"
// 结果:我叫小明,今年20岁

// 甚至可以放表达式
let sum = "5 + 3 = (5 + 3)"  // 5 + 3 = 8

原理(...) 里的内容会被转换成字符串,然后插入到原来的位置。

2.6 多行字符串

写长文本时,用三个双引号:

let poem = """
    春眠不觉晓,
    处处闻啼鸟。
    夜来风雨声,
    花落知多少。
    """

// 注意:开头的换行会被去掉,缩进以结尾的三个引号为基准

2.7 类型转换 - 不同类型不能直接运算

let integer = 8
let decimal = 3.14

// let result = integer + decimal  // ❌ 错误!类型不匹配

// 必须显式转换
let result = Double(integer) + decimal  // ✅ 11.14

// 整数相除要注意
let x = 10
let y = 3
let intDivision = x / y      // 3(整数除法,舍去小数)
let doubleDivision = Double(x) / Double(y)  // 3.333...

为什么 Swift 这么严格?

  • 防止意外丢失精度
  • 让代码意图更明确
  • 编译时就能发现错误

2.8 实际应用:温度转换器

// 摄氏度转华氏度:F = C × 9/5 + 32
let celsius = 25.0
let fahrenheit = celsius * 9 / 5 + 32
print("(celsius)°C = (fahrenheit)°F")  // 25.0°C = 77.0°F

// 华氏度转摄氏度:C = (F - 32) × 5/9
let f = 98.6
let c = (f - 32) * 5 / 9
print("(f)°F = (c)°C")  // 98.6°F = 37.0°C

第3章:集合类型 - 管理多个数据

3.1 为什么需要集合?

想象你要存一个班的学生成绩:

  • 用单个变量:score1, score2... 要写几十个,疯了!
  • 用数组:一个变量存所有成绩,还能批量处理

3.2 数组 (Array) - 有序列表

创建数组:

// 显式创建
var fruits: [String] = ["Apple", "Banana", "Orange"]

// 类型推断
var numbers = [1, 2, 3, 4, 5]

// 空数组(必须声明类型)
var emptyArray: [Int] = []
var anotherEmpty: [String] = Array()

// 创建重复元素的数组
var repeated = Array(repeating: "Hi", count: 5)
// ["Hi", "Hi", "Hi", "Hi", "Hi"]

访问和修改:

var shoppingList = ["Eggs", "Milk", "Bread"]

// 访问元素(索引从 0 开始)
let firstItem = shoppingList[0]   // "Eggs"
let secondItem = shoppingList[1]  // "Milk"

// 修改元素
shoppingList[0] = "Organic Eggs"

// 添加元素
shoppingList.append("Butter")     // 加在最后
shoppingList.insert("Cheese", at: 1)  // 插在指定位置

// 删除元素
shoppingList.remove(at: 0)        // 删除第一个
shoppingList.removeLast()         // 删除最后一个

// 获取数组信息
let count = shoppingList.count    // 元素个数
let isEmpty = shoppingList.isEmpty // 是否为空

⚠️ 注意数组越界:

let items = ["A", "B", "C"]
// let x = items[10]  // ❌ 运行时会崩溃!

// 安全访问
if items.count > 10 {
    let x = items[10]
}

3.3 数组的高级操作

遍历数组

let names = ["Alice", "Bob", "Charlie"]

// 方式1:for-in 循环
for name in names {
    print("Hello, (name)")
}

// 方式2:带索引
for (index, name) in names.enumerated() {
    print("(index + 1). (name)")
}
// 1. Alice
// 2. Bob
// 3. Charlie

// 方式3:forEach
names.forEach { name in
    print(name)
}

高阶函数 - 函数式编程

let numbers = [1, 2, 3, 4, 5]

// map:每个元素都转换
let doubled = numbers.map { $0 * 2 }
// [2, 4, 6, 8, 10]

let stringNumbers = numbers.map { String($0) }
// ["1", "2", "3", "4", "5"]

// filter:筛选符合条件的
let evens = numbers.filter { $0 % 2 == 0 }
// [2, 4]

let greaterThanThree = numbers.filter { $0 > 3 }
// [4, 5]

// reduce:汇总成一个值
let sum = numbers.reduce(0) { result, number in
    result + number
}
// 15

// 简写
let product = numbers.reduce(1) { $0 * $1 }
// 120 (1*2*3*4*5)

// sorted:排序
let unsorted = [3, 1, 4, 1, 5, 9]
let sorted = unsorted.sorted()
// [1, 1, 3, 4, 5, 9]

let descending = unsorted.sorted(by: >)
// [9, 5, 4, 3, 1, 1]

// 链式调用
let result = numbers
    .filter { $0 > 2 }      // [3, 4, 5]
    .map { $0 * 10 }        // [30, 40, 50]
    .reduce(0, +)           // 120

这些符号是什么意思?

  • $0:第一个参数(当前元素)
  • $1:第二个参数(对于 reduce 是累积值)
  • >:大于运算符,可以作为函数传递

3.4 字典 (Dictionary) - 键值对存储

想象查字典:你输入"苹果"(键),得到"apple"(值)。

// 创建字典
var scores: [String: Int] = [    "Alice": 95,    "Bob": 87,    "Charlie": 92]

// 类型推断
var capitals = [    "China": "Beijing",    "USA": "Washington",    "Japan": "Tokyo"]

// 空字典
var emptyDict: [String: Int] = [:]

访问和修改:

// 访问值(返回可选类型,因为可能不存在)
let aliceScore = scores["Alice"]        // Optional(95)
let davidScore = scores["David"]        // nil(不存在)

// 安全解包
if let score = scores["Alice"] {
    print("Alice scored (score)")
} else {
    print("No score found")
}

// 提供默认值(推荐!)
let bobScore = scores["Bob", default: 0]        // 87
let unknownScore = scores["David", default: 0]  // 0

// 修改/添加
scores["Alice"] = 98        // 修改已有值
scores["David"] = 88        // 添加新键值对

// 删除
scores.removeValue(forKey: "Bob")

遍历字典:

for (country, capital) in capitals {
    print("(country)'s capital is (capital)")
}

// 只遍历键
for country in capitals.keys {
    print(country)
}

// 只遍历值
for capital in capitals.values {
    print(capital)
}

3.5 集合 (Set) - 无序不重复

Set 就像数学里的集合:

  • 无序:没有顺序,不能通过索引访问
  • 不重复:同一个元素只能存在一次
// 创建 Set(必须用 Set 显式创建)
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip Hop"]

// 从数组创建
var numbers = Set([1, 2, 3, 3, 3, 3])  // {1, 2, 3}(重复被自动去掉)

// 空 Set
var emptySet = Set<String>()

Set 的独特操作:

let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

// 并集(所有元素)
oddDigits.union(evenDigits)
// {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// 交集(共同元素)
oddDigits.intersection(evenDigits)
// {}(空集,没有共同元素)

// 差集(在 A 中但不在 B 中)
oddDigits.subtracting(singleDigitPrimeNumbers)
// {1, 9}

// 对称差集(只在一个集合中的元素)
oddDigits.symmetricDifference(singleDigitPrimeNumbers)
// {1, 2, 9}

// 成员关系
let isSubset = singleDigitPrimeNumbers.isSubset(of: oddDigits)  // false
let isSuperset = oddDigits.isSuperset(of: [1, 3])  // true
let isDisjoint = oddDigits.isDisjoint(with: evenDigits)  // true(没有共同元素)

什么时候用 Set?

  • 需要确保元素唯一性(比如用户标签)
  • 经常需要做集合运算(交集、并集等)
  • 不需要保持顺序

3.6 实际应用:学生成绩管理系统

// 学生成绩
var studentScores: [String: [String: Int]] = [
    "Alice": ["Math": 95, "English": 88, "Science": 92],
    "Bob": ["Math": 78, "English": 85, "Science": 80],
    "Charlie": ["Math": 92, "English": 90, "Science": 95]
]

// 添加新学生
studentScores["David"] = ["Math": 88, "English": 92, "Science": 85]

// 计算每个学生的平均分
for (student, scores) in studentScores {
    let total = scores.values.reduce(0, +)
    let average = Double(total) / Double(scores.count)
    print("(student)'s average: (String(format: "%.1f", average))")
}

// 找出数学成绩最高的学生
let mathScores = studentScores.mapValues { $0["Math", default: 0] }
if let topStudent = mathScores.max(by: { $0.value < $1.value }) {
    print("Top in Math: (topStudent.key) with (topStudent.value)")
}

// 获取所有科目名称
var allSubjects = Set<String>()
for scores in studentScores.values {
    allSubjects.formUnion(scores.keys)
}
print("All subjects: (allSubjects)")

第4章:控制流 - 程序的逻辑

4.1 if 语句 - 条件判断

let temperature = 25

if temperature > 30 {
    print("好热!")
} else if temperature > 20 {
    print("天气不错")  // 会执行这一行
} else {
    print("有点冷")
}

条件可以是任何返回 Bool 的表达式:

let age = 20
let hasID = true

if age >= 18 && hasID {
    print("可以进入")
}

// 逻辑运算符
// &&  与(两边都为 true 才为 true)
// ||  或(一边为 true 就为 true)
// !   非(取反)

4.2 switch 语句 - 多分支判断

Swift 的 switch 非常强大,比 if-else 更清晰:

let character: Character = "a"

switch character {
case "a":
    print("第一个字母")
case "z":
    print("最后一个字母")
default:
    print("其他字母")
}

switch 的特点:

let someCharacter: Character = "e"

switch someCharacter {
case "a", "e", "i", "o", "u":
    print("(someCharacter) 是元音")
case "b", "c", "d", "f", "g":  // 可以多个 case 合并
    print("(someCharacter) 是辅音")
default:
    print("(someCharacter) 不是字母")
}
// 注意:Swift 的 switch 默认不会"穿透"到下一个 case,不需要写 break

范围匹配:

let count = 62
let things = "students"

switch count {
case 0:
    print("没有 (things)")
case 1..<5:  // 1 到 4
    print("有 (count) 个 (things)")
case 5..<12:
    print("有几个 (things)")
case 12..<100:
    print("有很多 (things)")  // 会执行这一行
case 100..<1000:
    print("有数百个 (things)")
default:
    print("有非常多 (things)")
}

元组匹配(很强大!):

let somePoint = (1, 1)

switch somePoint {
case (0, 0):
    print("在原点")
case (_, 0):  // _ 表示"不管这个值是什么"
    print("在 x 轴上")
case (0, _):
    print("在 y 轴上")
case (-2...2, -2...2):  // 范围匹配
    print("在原点附近")
default:
    print("在其他地方")
}

值绑定:


switch anotherPoint {
case (let x, 0):  // 把 x 坐标绑定到常量 x
    print("在 x 轴上,x = (x)")
case (0, let y):
    print("在 y 轴上,y = (y)")
case let (x, y):  // 把两个坐标都绑定
    print("在 ((x), (y))")
}

where 子句 - 附加条件:

let yetAnotherPoint = (1, -1)

switch yetAnotherPoint {
case let (x, y) where x == y:
    print("((x), (y)) 在对角线上")
case let (x, y) where x == -y:
    print("((x), (y)) 在反对角线上")  // 会执行
default:
    print("普通点")
}

4.3 for-in 循环 - 遍历

// 遍历范围
for index in 1...5 {
    print("(index) 乘以 5 是 (index * 5)")
}

// 遍历数组
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("你好, (name)!")
}

// 遍历字典
let numberOfLegs = ["蜘蛛": 8, "蚂蚁": 6, "猫": 4]
for (animalName, legCount) in numberOfLegs {
    print("(animalName)s 有 (legCount) 条腿")
}

// 如果不需要索引,用 _ 忽略
let base = 3
let power = 10
var answer = 1
for _ in 1...power {  // 不需要用到循环变量
    answer *= base
}
print("(base) 的 (power) 次方是 (answer)")

// 使用 stride 跳过某些值
let minutes = 60
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    print("刻度 (tickMark)")
}
// 输出 0, 5, 10, 15... 55

4.4 while 循环 - 条件循环

// while:先检查条件,再执行
var i = 1
while i < 5 {
    print(i)
    i += 1
}
// 输出 1, 2, 3, 4

// repeat-while:先执行一次,再检查条件(至少执行一次)
var j = 5
repeat {
    print(j)
    j += 1
} while j < 5
// 输出 5(虽然条件不满足,但已经执行了一次)

什么时候用 while?

  • 不知道要循环多少次
  • 等待某个条件成立
  • 读取文件直到文件结束

4.5 控制转移语句

break - 立即结束整个循环

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]

for character in puzzleInput {
    if charactersToRemove.contains(character) {
        continue  // 跳过当前这次循环,继续下一次
    }
    puzzleOutput.append(character)
}
print(puzzleOutput)  // "grtmndsthnklk"

break - 立即结束整个循环

let numberSymbol: Character = "三"
var possibleIntegerValue: Int?

switch numberSymbol {
case "1", "一":
    possibleIntegerValue = 1
case "2", "二":
    possibleIntegerValue = 2
case "3", "三":
    possibleIntegerValue = 3
case "4", "四":
    possibleIntegerValue = 4
default:
    break  // 什么都不做,直接结束 switch
}

if let integerValue = possibleIntegerValue {
    print("整数值是 (integerValue)")
} else {
    print("无法转换为整数")
}

4.6 guard - 提前返回模式

guard 是 Swift 特有的,用来检查必要条件:

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        print("没有名字")
        return  // 必须退出当前作用域
    }
    
    print("你好, (name)!")
    
    // name 在这里可以直接使用(已经解包)
    print("再次问候 (name)")
}

greet(person: ["name": "John"])  // 你好, John!
greet(person: [:])                // 没有名字

guard vs if:

  • if:条件为 true 时执行代码块
  • guard:条件为 false 时执行 else 代码块,必须退出

什么时候用 guard?

  • 检查函数参数是否有效
  • 检查可选值是否有值
  • 提前返回,减少嵌套层级

对比:

// 不用 guard(嵌套层级深)
func process1(value: Int?) {
    if let v = value {
        if v > 0 {
            if v < 100 {
                print("处理 (v)")
            }
        }
    }
}

// 用 guard(扁平结构,更易读)
func process2(value: Int?) {
    guard let v = value else { return }
    guard v > 0 else { return }
    guard v < 100 else { return }
    
    print("处理 (v)")
}

4.7 defer - 延迟执行

defer 里的代码会在当前作用域结束时执行,无论怎么退出:

func processFile(filename: String) {
    print("打开文件 (filename)")
    
    defer {
        print("关闭文件 (filename)")  // 无论如何都会执行
    }
    
    if filename.isEmpty {
        print("文件名无效")
        return  // 这里会触发 defer
    }
    
    print("处理文件内容...")
    // 函数结束也会触发 defer
}

processFile(filename: "test.txt")
// 输出:
// 打开文件 test.txt
// 处理文件内容...
// 关闭文件 test.txt

processFile(filename: "")
// 输出:
// 打开文件
// 文件名无效
// 关闭文件

多个 defer:

func multipleDefers() {
    defer { print("1") }
    defer { print("2") }
    defer { print("3") }
    print("中间")
}

multipleDefers()
// 输出:
// 中间
// 3
// 2
// 1
//(后进先出,像栈一样)

实际应用 - 资源管理:

func copyFile(from source: String, to destination: String) throws {
    let input = try openFile(source)
    defer { closeFile(input) }
    
    let output = try createFile(destination)
    defer { closeFile(output) }
    
    // 复制内容...
    // 无论这里发生什么错误,两个文件都会被关闭
}

第5章:函数 - 代码的复用

5.1 为什么要用函数?

想象你要算圆的面积:

  • 不用函数:每次都要写 3.14 * radius * radius
  • 用函数:写一次 circleArea(radius: 5),到处都能用

好处:

  • 代码复用
  • 逻辑清晰
  • 易于维护
  • 便于测试

5.2 定义和调用函数

// 定义函数
func greet(person: String) -> String {
    let greeting = "Hello, (person)!"
    return greeting
}

// 调用函数
print(greet(person: "Anna"))   // Hello, Anna!
print(greet(person: "Brian"))  // Hello, Brian!

函数结构:

func 函数名(参数名: 参数类型) -> 返回类型 {
    // 函数体
    return 返回值
}

5.3 参数标签和参数名

Swift 函数可以有两个名字:参数标签(调用时用)和 参数名(函数体内用):

// 外部名 from,内部名 hometown
func greet(person: String, from hometown: String) -> String {
    return "Hello (person)! Glad you could visit from (hometown)."
}

// 调用时使用外部名
print(greet(person: "Bill", from: "Cupertino"))
// Hello Bill! Glad you could visit from Cupertino.

省略参数标签:

// 用 _ 省略外部名,调用时不用写参数名
func multiply(_ a: Int, _ b: Int) -> Int {
    return a * b
}

multiply(3, 4)  // 12(不用写 multiply(a: 3, b: 4))

什么时候省略?

  • 函数名已经表达了参数的含义
  • 数学运算函数
  • 让调用更像自然语言

5.4 默认参数值

    return "Hello (person)! Glad you could visit from (hometown)."
}

// 使用默认值
greetWithDefaults(person: "Dave")  // Hello Dave! Glad you could visit from Cupertino.

// 覆盖默认值
greetWithDefaults(person: "Dave", from: "Beijing")  // Hello Dave! Glad you could visit from Beijing.

注意: 带默认值的参数要放在参数列表最后。

5.5 可变参数

func sum(_ numbers: Int...) -> Int {
    var total = 0
    for number in numbers {
        total += number
    }
    return total
}

sum(1, 2, 3, 4, 5)  // 15
sum()               // 0
sum(100)            // 100

Int... 表示"零个或多个 Int 参数",在函数体内它变成一个数组 [Int]

5.6 多返回值 - 用元组

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    if array.isEmpty { return nil }
    
    var currentMin = array[0]
    var currentMax = array[0]
    
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    
    return (min: currentMin, max: currentMax)
}

// 使用
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
    print("最小值是 (bounds.min),最大值是 (bounds.max)")
    // 最小值是 -6,最大值是 109
}

元组的好处:

  • 一次返回多个值
  • 可以给每个值命名
  • 可以是可选的(整个元组都可能是 nil)

5.7 函数作为参数和返回类型

函数作为参数:

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
}

let numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)  // true

函数作为返回类型:

func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var runningTotal = 0
    
    func incrementer() -> Int {
        runningTotal += incrementAmount
        return runningTotal
    }
    
    return incrementer
}

let incrementByTen = makeIncrementer(incrementAmount: 10)
incrementByTen()  // 10
incrementByTen()  // 20
incrementByTen()  // 30

let incrementBySeven = makeIncrementer(incrementAmount: 7)
incrementBySeven()  // 7
incrementByTen()    // 40(独立的 runningTotal)

5.8 嵌套函数

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    
    return backward ? stepBackward : stepForward
}

var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)

while currentValue != 0 {
    print("(currentValue)...")
    currentValue = moveNearerToZero(currentValue)
}
print("零!")

第6章:闭包 - 匿名函数

6.1 什么是闭包?

闭包是自包含的代码块,可以在代码中传递和使用。Swift 中的闭包类似于 C 和 Objective-C 中的 block,或者其他语言中的 lambda。

三种形式:

  1. 全局函数:有名字,不捕获值
  2. 嵌套函数:有名字,可以捕获值
  3. 闭包表达式:没有名字,可以捕获值

6.2 闭包表达式语法

// 完整语法
{ (参数) -> 返回类型 in
    // 语句
}

// 示例:排序闭包
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 用闭包排序
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

6.3 闭包表达式简化

第一步:类型推断(编译器知道参数和返回类型)

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

第二步:隐式返回(单表达式可以省略 return)

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

第三步:简写参数名(用 0,0, 1 代替参数名)

let reversedNames = names.sorted(by: { $0 > $1 })
// $0 是第一个参数,$1 是第二个参数

第四步:运算符函数(可以直接传运算符)

let reversedNames = names.sorted(by: >)

6.4 尾随闭包

如果闭包是函数的最后一个参数,可以用尾随闭包语法:

// 正常写法
let reversedNames = names.sorted(by: { $0 > $1 })

// 尾随闭包
let reversedNames = names.sorted { $0 > $1 }

多行尾随闭包(更常见):

    0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]

let numbers = [16, 58, 510]

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// strings = ["OneSix", "FiveEight", "FiveOneZero"]

6.5 值捕获

闭包可以从周围上下文捕获常量和变量:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    
    func incrementer() -> Int {
        runningTotal += amount      // 捕获 runningTotal 和 amount
        return runningTotal
    }
    
    return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen()  // 10
incrementByTen()  // 20
incrementByTen()  // 30

// 再创建一个,是独立的
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()  // 7
incrementByTen()    // 40(还是原来的)

Swift 会自动管理内存:

  • 如果不需要,不会捕获
  • 如果需要,会捕获引用

6.6 逃逸闭包 (@escaping)

当闭包作为参数传递给函数,但在函数返回后才执行,就是逃逸闭包

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)  // 先存起来,稍后执行
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()  // 立即执行
}

为什么要标记 @escaping?

  • 让调用者知道闭包可能晚执行
  • 涉及内存管理(需要显式处理 self)

6.7 自动闭包 (@autoclosure)

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 普通闭包
func serve(customer customerProvider: () -> String) {
    print("Now serving (customerProvider())!")
}

serve(customer: { customersInLine.removeFirst() })

// 自动闭包(自动把表达式变成闭包)
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving (customerProvider())!")
}

serve(customer: customersInLine.removeFirst())  // 不用写 {}

注意: @autoclosure 一般用于断言、日志等调试功能,不要滥用。