5月27日 10:38

Swift 错误处理怎么做?throws、try、catch 详解

Swift 的错误处理核心就四个关键字:Error 定义错误、throws 声明可能失败、throw 抛出错误、try+do-catch 捕获处理。整个流程编译器强制检查——throws 函数必须被 try 调用,try 必须在 do-catch 或另一个 throws 函数中,不可能"忘了处理错误"。这点和 Java 的 checked exception 思路类似,但 Swift 更轻量。

try 三种形式各有用途:try(标准,需 do-catch)、try?(失败返回 nil,丢弃错误详情)、try!(断言不会失败,否则 crash)。日常 80% 场景用 try? 就够了——只关心成功还是失败,不需要区分原因。需要根据错误类型走不同分支时才用完整 do-catch。try! 只在逻辑上不可能失败的路径或单元测试中用。

错误沿调用链自动向上传播:A throws → B 调用 A 也必须 throws → C 调用 B 也必须处理,直到某个调用方用 do-catch 消化。传播链编译器强制检查,不存在运行时才发现错误没处理的情况。

defer 无论是否抛错都执行,用于资源清理,多个 defer 按栈序(后进先出)执行。rethrows 专用于高阶函数——函数本身不抛错,但传入闭包如果是 throws 的,调用方也必须 try。标准库 map、filter、forEach 都用了 rethrows:传普通闭包不强制 try,传 throws 闭包才需要。

Swift 5 的 Result<Success, Failure> 把错误从控制流变成值,可以像处理枚举一样处理成功和失败,特别适合异步回调。async/await 普及后新代码用 async throws 更直观,Result 主要兼容旧接口。Swift 错误处理不是传统"异常"——不会 unwinding 调用栈,happy path 几乎零开销。

追问

try? 和 try! 有什么区别?什么场景用哪个?

try? 失败返回 nil;try! 失败直接 crash。绝大多数场景用 try?,比如读配置文件读不到就用默认值。try! 只在逻辑上不可能失败时用,比如解析 Bundle 内的 JSON——这都能失败说明资源缺失,是开发阶段就该暴露的 bug,不应静默处理。

rethrows 和 throws 有什么区别?

throws 表示函数自身会抛错;rethrows 表示函数本身不抛错,但透传闭包参数的错误。关键区别:rethrows 函数传入不抛错的闭包时,调用方不需要 try。Array.map 用 rethrows,所以 arr.map { $0 + 1 } 不需要 try,arr.map { try throwingFunc($0) } 才需要。

Result 和 do-catch 怎么选?

同步用 do-catch;异步回调用 Result,因为 completion handler 里抛出的错误外层 catch 不到。async/await 之后新代码直接 async throws 更清晰。Result(catching: { try func() }) 可以把 throws 函数包装成 Result,方便在回调中传递。

defer 有什么常见坑?

多个 defer 后进先出。坑一:在循环里写 defer 以为每次迭代都执行,实际等函数作用域结束。坑二:defer 不等异步操作完成,需要异步清理得用其他机制。正确做法:打开资源后立刻写 defer 关闭。

Swift 错误处理和 Java 异常有什么本质区别?

Swift 的错误不会 unwinding 调用栈,happy path 零开销。Java checked 异常强制 catch 或声明,代码侵入性强;Swift 更轻量,try? 提供了快速降级路径。Swift 没有 finally,用 defer 替代,且 defer 不限于 do-catch 块,任何作用域都能用。

写段代码

swift
enum APIError: Error { case invalidURL case badStatus(Int) } func fetchUser(id: String) throws -> User { guard let url = URL(string: "https://api.example.com/users/\(id)") else { throw APIError.invalidURL } let (data, resp) = try URLSession.shared.data(from: url) guard let http = resp as? HTTPURLResponse, http.statusCode == 200 else { throw APIError.badStatus((resp as? HTTPURLResponse)?.statusCode ?? -1) } return try JSONDecoder().decode(User.self, from: data) } do { let user = try fetchUser(id: "42") } catch APIError.badStatus(let code) { print("HTTP \(code)") } catch { print(error) } let maybe = try? fetchUser(id: "42") // User?
标签:Swift