try、try? 和 try! 的区别是什么?
在 Swift 中,try、try? 和 try! 都是用来调用可能会抛出错误(marked with throws)的函数的关键字。它们的主要区别在于如何处理错误以及返回值的类型。
简单总结如下:
try:标准的错误处理。需要配合do-catch语句块使用,或者在抛出错误的函数中继续向上传递错误。try?:将结果转换为可选值 (Optional)。如果成功返回Optional(值),如果失败返回nil(忽略具体错误信息)。try!:强制执行。断言该调用绝对不会出错。如果成功返回解包后的值,如果失败则导致程序崩溃 (Runtime Crash)。
下面是详细的解释和代码示例:
1. try (标准错误处理)
这是最常用的方式。当你想要捕获并处理具体的错误信息时使用。你必须把它放在 do-catch 代码块中,或者在一个同样标记为 throws 的函数内部使用。
- 返回值:原始类型(如果成功)。
- 错误处理:跳转到
catch块。
plaintext
enum FileError: Error {
case fileNotFound
case noPermission
}
func readFile(name: String) throws -> String {
if name == "" { throw FileError.fileNotFound }
return "文件内容"
}
// 使用 try
do {
let content = try readFile(name: "data.txt")
print("读取成功: \(content)")
} catch {
// 这里可以获取具体的 error 信息
print("读取失败: \(error)")
}
2. try? (转换为可选值)
当你不关心具体的错误原因,只关心“成功了没有”时使用。它会将原本的错误处理机制简化为返回 nil。
- 返回值:
Optional<T>(例如String?)。 - 错误处理:如果抛出错误,结果为
nil,程序继续执行,不会崩溃。
plaintext
// 成功的情况:result 是 Optional("文件内容")
let result = try? readFile(name: "data.txt")
// 失败的情况:failedResult 是 nil
let failedResult = try? readFile(name: "")
// 常用配合 guard let 使用
if let content = try? readFile(name: "data.txt") {
print(content)
} else {
print("读取失败,但我不在乎具体是什么错误")
}
3. try! (禁止错误传递 / 强制执行)
当你百分之百确定这个函数调用在当前情况下绝对不会失败,或者如果它失败了,你认为程序应该直接停止运行(因为发生了不可能发生的逻辑错误)时使用。它类似于强制解包 (!)。
- 返回值:原始类型
T(非 Optional)。 - 错误处理:如果抛出错误,程序直接崩溃 (Crash)。
plaintext
// 你确定文件名正确,绝对不会出错
let content = try! readFile(name: "config.json")
// content 类型是 String,不是 String?
// 如果你判断失误,函数真的抛出了错误:
// let crash = try! readFile(name: "")
// 💥 Fatal error: 'try!' expression unexpectedly raised an error
总结对比表
| 关键字 | 是否需要 do-catch | 返回值类型 | 发生错误时的行为 | 适用场景 |
|---|---|---|---|---|
try |
是 (或继续 throws) | T |
跳转到 catch 块 | 需要处理具体错误,或保证流程完整性。 |
try? |
否 | T? (Optional) |
返回 nil |
不关心具体错误信息,只在乎成功与否。 |
try! |
否 | T |
程序崩溃 | 100% 确定不会出错,通常用于加载应用必须的资源或测试代码。 |
最佳实践建议
- 首选
try+do-catch,这是最安全、最标准的做法。 - 如果错误处理逻辑仅仅是“如果失败就不做了”,使用
try?可以让代码更简洁。 - 慎用
try!。除非是在加载 App 必须的本地资源(如打包在 Bundle 里的图片或 JSON),且你确定资源一定存在。如果在网络请求或用户输入处理中使用try!,一旦出现异常会导致 App 闪退,用户体验极差。