guard 与提前退出
本教程共 93 篇 · 第 27 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:学会用 guard 在条件不满足时提前退出,并用它替代层层嵌套的 if-let,让函数的主干代码保持平整。
写函数时你经常要先检查一堆前置条件:“参数有值吗?权限够吗?数据合法吗?”任何一项不达标就该立刻返回。Swift 专门为此准备了 guard 语句,它的中文意思就是“守卫”。它和 if 方向相反,却能把代码写得清爽得多。
1-1 guard 的基本形态
guard 后面跟一个条件,再跟 else 块。条件成立,程序正常往下走;不成立,就执行 else 块里的代码——而且 else 块必须把当前作用域结束掉。
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("你好 \(name)!")
guard let location = person["location"] else {
print("祝你那里天气不错。")
return
}
print("祝你 \(location) 的天气不错。")
}
第一次调用只给了 name,第一个 guard 通过,第二个 guard 因为没 location 进 else 打印一句就 return。guard 把“检查 + 失败即返回”浓缩成一行,主干逻辑不被埋在嵌套里。
Note
guard的else块必须通过return、break、continue、throw,或者调用fatalError这类“不会返回”的函数来退出当前作用域。只写打印而不退出会编译报错,因为作用域根本没结束。
1-2 guard 和 if-let 的核心差别
用 if-let 也能做同样的检查,但写法差别很大:
func greetWithIf(person: [String: String]) {
if let name = person["name"] {
print("你好 \(name)!")
if let location = person["location"] {
print("祝你 \(location) 的天气不错。")
} else {
print("祝你那里天气不错。")
}
} else {
return
}
}
看出问题了吗?每多一个检查,代码就往右缩进一层,形成“金字塔”式的嵌套。条件一多,主干逻辑被挤到最右边,可读性直线下降。这就是初学者常吐槽的“嵌套地狱”。
guard 的好处是:检查不通过就走人,通过才继续,主干不用缩进。条件一个个排下来,像门卫逐个放行,全过了才进到真正干活的地方。读代码的人不用穿过层层花括号去找主干。
1-3 解包的值会留下来
guard let name = ... 里绑定出来的 name,在 guard 之后的整个函数体里都能直接用。这是它和 if-let 的另一大区别:if-let 绑定的变量只在那个 if 块内有效,而 guard 绑定的变量在块外继续有效。
func process(user: [String: String]) {
guard let id = user["id"] else { return }
// 这里还能用 id
print("处理用户 \(id)")
}
因为 guard 失败时已经退出,能走到 print 这行,说明 id 一定有值。编译器知道这点,所以 id 在后面是普通的非可选值,不用再解包。这个特性让 guard 在“先验证、后使用”的场景里极其顺手,省去反复解包的麻烦。
1-4 什么时候用 guard 最合适
凡是“不满足条件就不该继续”的校验,都适合 guard。典型例子:
- 函数入口检查必填参数是否存在;
- 解析数据前确认格式合法;
- 访问可选值前确认它不为
nil; - 权限或状态不满足时提前返回。
它把“错误处理”集中在函数开头,真正的业务逻辑干干净净地留在后面,读代码的人一眼就能分清“哪些是门槛、哪些是正文”。
Tip经验法则:如果失败后要退出当前作用域,用
guard;如果失败后仍要在这个作用域里继续做别的事,用if。两者不是互斥的,按语义选,很多时候一个函数里两者会同时出现。
1-5 在循环里也能用 guard
guard 不只活在函数里。在循环里,配合 continue 可以优雅地“跳过不合格的元素”:
let scores = ["A": 90, "B": nil, "C": 75]
for (name, value) in scores {
guard let score = value else {
continue
}
print("\(name) 得了 \(score) 分")
}
遇到 nil 就 continue 跳过,剩下的正常打印。guard 的 else 用 continue 退出本次循环迭代,完全合法。比起在循环里写 if let { ... } 把正常逻辑裹进一层,这种写法主干更平。
1-6 初学者常见的误解
第一,以为 guard 可以没有 else。不行,guard 必须有 else,且 else 必须退出作用域,这是语法硬性要求。
第二,把 guard 当成 if 的简写。它们语义相反:if 是“成立就做事”,guard 是“不成立就退出,成立就放行”。别混用,否则读起来会很别扭。
第三,在 guard 的 else 里只打日志不退出。这样编译器会报错,因为 else 没有真正结束作用域。要么退出,要么别用 guard,改用 if。
1-7 guard 不止能解包
虽然 guard let 最常见,但 guard 后面也可以是任意布尔条件,甚至能配合可用性检查 #available 使用。比如只有系统版本够新才继续,否则提前返回旧逻辑:
guard #available(macOS 10.12, *) else {
return "默认灰色"
}
这种写法把“环境不满足就退出”也收进了 guard 的统一风格里。只要条件是一个能算出 true/false 的表达式,就能放在 guard 后面。
1-8 if 与 guard 速查对照
用一张表帮你决定用哪个:
- 失败后要退出当前作用域(函数/循环)→ 用
guard; - 失败后还要在这个作用域继续做事 → 用
if; - 想把解包出的值留到后面用 →
guard更自然; - 只在块内临时用一下 →
if let更贴身。
一句话总结:guard 是“守门员”,不合格就拦在门外;if 是“分流器”,合格的进去干活、不合格的在别处处理。guard 让函数开头像一排门禁,过了才能进正文。
guard 是 Swift 提升代码可读性的利器。掌握它之后,你的函数会更平、更直、更像在讲“先过门槛,再干活”的故事。流程控制模块到此结束,下一模块我们进入函数,看看如何把代码打包成可复用的块。
1-9 guard 在初始化器里的妙用
guard 不只用在普通函数,在“初始化器”(后面讲类和结构体时会细说)里也极好用。初始化器的任务是把实例的各属性都安置好,任何前置条件不满足就该立刻失败。用 guard 在初始化开头做校验,能让后面的属性赋值逻辑干干净净。
struct Temperature {
let celsius: Double
init?(celsius: Double) {
guard celsius >= -273.15 else { return nil }
self.celsius = celsius
}
}
这个例子里,温度不能低于绝对零度,所以 guard 挡住非法值、直接 return nil 让初始化失败。把这类“门槛检查”写在最前,后面的赋值才安心。这种“先守门、再干活”的节奏,正是 guard 的精髓。
如果你之前习惯用 if 做校验,刚开始改 guard 可能觉得别扭。别急,先从一个函数入口的几个必填参数练起:把每个“没有就返回”的检查都换成 guard,你会立刻发现函数主干不再缩进成金字塔。等这种“门禁式”写法变成肌肉记忆,你写出来的函数自然会既安全又清爽。这也是 Swift 社区特别推崇 guard 的原因。