为什么 Swift 的控制流值得单开一篇?
- 语法糖多:区间、stride、tuple、where、guard、defer......
- 安全严苛:switch 必须 exhaustive、case 不能空、默认不贯穿。
- 表达能力强:if/switch 可以当表达式用,一行赋值即可。
- 场景丰富:从日常循环到资源清理、API 可用性检查,全覆盖。
For-In 循环:从"会写"到"写对"
基本形态
swift
// 遍历数组
let fruits = ["apple", "orange", "banana"]
for fruit in fruits {
print("我喜欢吃\(fruit)")
}
字典遍历注意顺序
swift
let legCount = ["ant": 6, "snake": 0, "cat": 4]
// 字典无序!同一台机器多次运行顺序都可能不同
for (animal, legs) in legCount {
print("\(animal) 有 \(legs) 条腿")
}
区间与"忽略值"
swift
// 闭区间 ... 包含两端
for i in 1...5 {
print("5 x \(i) = \(5 * i)")
}
// 半开区间 ..< 忽略最后一项
let minutes = 0..<60 // 0~59
for tick in minutes where tick % 5 == 0 {
// 只打印 0/5/10/.../55
print("表盘刻度 \(tick)")
}
// 如果根本不想用下标,用 _ 占位
var base = 1
for _ in 1...10 { // 3 的 10 次方
base *= 3
}
print("3^10 = \(base)") // 59049
stride 灵活跳步
swift
// 开区间 stride(from:to:by:)
for degree in stride(from: 0, to: 360, by: 30) {
print("旋转 \(degree)°")
}
// 闭区间 stride(from:through:by:)
for rate in stride(from: 0.5, through: 1.5, by: 0.25) {
print("汇率 \(rate)")
}
While 与 Repeat-While:何时选谁?
场景 | 推荐 |
---|---|
可能一次都不执行 | while |
至少执行一次 | repeat-while |
蛇梯棋:同一逻辑两种写法
swift
let finalSquare = 25
var board = Array(repeating: 0, count: finalSquare + 1)
// 梯子
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
// 蛇
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
// ----- while 版 -----
var square = 0, diceRoll = 0
while square < finalSquare {
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 } // 模拟 1~6 骰子
square += diceRoll
if square < board.count { square += board[square] }
}
print("while 版到达终点")
// ----- repeat-while 版 -----
square = 0; diceRoll = 0
repeat {
square += board[square] // 先结算梯子/蛇
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
square += diceRoll
} while square < finalSquare
print("repeat-while 版到达终点")
经验:
- 必须先"爬梯子/滑蛇"再"掷骰子"时,用
repeat-while
可以省一次越界检查。 - 其余情况
while
可读性更高。
条件语句:if / guard / switch 全维度对比
if 表达式(Swift 5.9+)
swift
let temp = 26
// 一行赋值,不再需要三目嵌套
let advice = if temp <= 0 { "穿羽绒服" }
else if temp >= 30 { "短袖+冷饮" }
else { "正常穿衣" }
print(advice) // 正常穿衣
注意:
- 所有分支必须返回同一类型,否则需要显式标注类型。
- 可以抛错:
let level = if temp > 100 { throw TempError.boiling } else { "ok" }
guard:提前退出,减少金字塔
swift
func buy(age: Int, stock: Int) {
guard age >= 18 else {
print("未成年禁止购买")
return
}
guard stock > 0 else {
print("库存不足")
return
}
// 以下代码一定是成年人且有库存
print("购买成功")
}
guard 与 if 的区别:
- 必须带
else
; else
内必须中断控制流(return/break/continue/throw/fatalError);- 解绑变量作用域延续到后续代码,避免多层嵌套。
switch:模式匹配大杀器
区间 & 复合值
swift
let score = 87
switch score {
case 90...100: print("优秀")
case 80..<90: print("良好")
case 60..<80: print("及格")
default: print("不及格")
}
tuple + where 条件
swift
let point = (2, 2)
switch point {
case (0, 0): print("原点")
case (let x, 0): print("在 x 轴,x=\(x)")
case (0, let y): print("在 y 轴,y=\(y)")
case (let x, let y) where x == y: print("在对角线 x=y 上")
default: print("其他")
}
值绑定与复合 case
swift
// 复合 case 共享同一段代码
switch "e" {
case "a", "e", "i", "o", "u": print("小写元音")
case "b", "c", "d", "f", "g": print("部分辅音")
default: print("其他字符")
}
// 复合 case 也支持绑定,但要保证类型一致
switch (2, 0) {
case (let d, 0), (0, let d): // 两个 pattern 都绑定 d 且类型一致
print("到轴距离为 \(d)")
default:
break
}
贯穿:显式 fallthrough
swift
let integerToDescribe = 5
var description = "数字 \(integerToDescribe) 是"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
description += " 质数,且"
fallthrough // 继续执行下一个 case
default:
description += " 是整数。"
}
print(description) // 数字 5 是 质数,且 是整数。
控制转移语句:continue / break / fallthrough / return / throw
continue & break 的最常见误区
swift
// 去掉小写元音与空格
let puzzleInput = "great minds think alike"
let vowels: Set<Character> = ["a", "e", "i", "o", "u", " "]
var output = ""
for ch in puzzleInput {
if vowels.contains(ch) {
continue // 立即进入下一轮
}
output.append(ch)
}
print(output) // grtmndsthnklk
带标签的语句:精确跳出血腥嵌套
swift
var finalSquare = 25
var square = 0
gameLoop: while true {
let dice = Int.random(in: 1...6)
switch square + dice {
case finalSquare:
print("刚好到达,游戏胜利")
break gameLoop // 跳出 while,不是跳出 switch
case let n where n > finalSquare:
print("点数太大,重新掷")
continue gameLoop // 继续 while 下一轮
default:
square += dice
}
}
defer:作用域退出时的"扫尾"利器
swift
func updateScore() {
var score = 10
if Bool.random() {
score += 5
defer { print("本次加分已落地,当前分数:\(score)") } // 1️⃣ 先写后执行
defer { score -= 100 } // 2️⃣ 再写先执行
print("离开 if 前 score=\(score)") // 15
}
print("离开函数前 score=\(score)") // -85
}
updateScore()
规则小结:
- 同一作用域多个
defer
以栈顺序执行(先注册后执行)。 - 无论正常 return、break、continue、throw 都会执行;进程崩溃除外。
- 典型场景:文件句柄/锁/数据库事务配对释放。
API 可用性检查:让旧系统安心升级
swift
if #available(iOS 15, macOS 12, *) {
// 仅 iOS15+/macOS12+ 能走到这里
print("iOS15+/macOS12+")
} else {
// 低版本走兼容方案
print("iOS15以下或者macOS12以下或者其他系统,比如Linex、visionOS")
}
// guard 写法,提前 return
func useNewAPI() {
guard #available(macOS 12, *) else { return }
// 以下代码编译器保证只在 macOS12+ 运行
}
总结
控制流元素 | 关键记忆点 |
---|---|
for-in | 可遍历任何 Sequence;字典无序;stride 可跳步 |
while vs repeat-while | 是否"先检查" |
if 表达式 | 同类型、可抛错、可函数返回值 |
guard | 必须 else 中断,解绑变量作用域外可用 |
switch | exhaustive、默认不贯穿、支持 tuple/where/值绑定 |
defer | 栈式延迟,资源清理黄金搭档 |
#available | 编译期+运行期双重保险 |
扩展思考:把知识迁移到日常开发
-
日志场景
用
defer
在函数出口统一打印耗时,避免早期 return 漏埋点。 -
资源池
文件句柄获取后立刻写
defer { fclose(fp) }
,再也不怕忘记关文件。 -
交互式 UI
利用
stride(from: 0, to: 360, by: 30)
生成圆形按钮坐标,一行代码搞定数学。 -
数据校验
多层
guard
提前返回,把"非法输入"挡在门外,主流程保持一级缩进。
控制流不是"写得出",而是"写得对、写得优雅"。