第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 // -128 到 127
let medium: Int16 = 32767 // -32768 到 32767
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。
三种形式:
- 全局函数:有名字,不捕获值
- 嵌套函数:有名字,可以捕获值
- 闭包表达式:没有名字,可以捕获值
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 })
第三步:简写参数名(用 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 一般用于断言、日志等调试功能,不要滥用。