Swift 中的错误处理:try/catch 及其他
iOS 和 macOS 开发需要可靠的错误管理。Swift 提供了一种强大而优雅的故障处理机制,基于关键字 throws、try、catch 和 do。在本文中,我们将分析如何正确处理错误、编写可读代码并避免常见陷阱。
1. 基础:Error 协议和 throws 关键字
Swift 中的任何错误都必须符合 Error 协议。通常使用枚举 (enum) 来实现:
enum NetworkError: Error { case badURL case noData case decodingFailed(String)}可能抛出错误的函数使用关键字 throws 标记:
func fetchUser(from urlString: String) throws -> User { guard let url = URL(string: urlString) else { throw NetworkError.badURL } // ... 请求逻辑}现在,当调用此类函数时,编译器将强制您处理可能的错误。
2. do-catch 结构:错误捕获
最常见的处理方式是 do-catch 块。您将潜在的危险代码放在 do 中,并在 catch 中处理故障:
do { let user = try fetchUser(from: "https://api.example.com/user") print("User loaded: \\(user.name)")} catch NetworkError.badURL { print("无效的 URL")} catch NetworkError.noData { print("服务器未返回数据")} catch NetworkError.decodingFailed(let message) { print("解码错误:\\(message)")} catch { print("未知错误:\\(error.localizedDescription)")}重要提示:最后一个没有指定具体类型的 catch 块会捕获所有剩余错误——建议始终添加它。
2.1. catch 中的模式匹配
您可以使用更复杂的条件:
do { let result = try riskyOperation()} catch let error as NetworkError where error == .badURL { print("URL 出现问题")} catch { print(error)}3. 替代方案:try? 和 try!
并非总是需要完整的处理。Swift 提供了两种简化的方法:
3.1. try? — 可选值
如果错误不严重,可以将结果转换为可选类型。发生故障时,函数将返回 nil:
let user = try? fetchUser(from: "https://example.com/user")// user 的类型是 User? — 出错时为 nil非常适合可以忽略错误的情况(例如,加载头像)。
3.2. try! — 强制执行
仅在您绝对确定不会出错时使用 try!。否则——应用程序会崩溃:
let user = try! fetchUser(from: "https://valid-url.com/user")// 如果 URL 无效——应用程序将崩溃在测试中或在程序逻辑上不可能发生故障的情况下使用 try!。
4. 高级技术:rethrows 和 deferred
4.1. 用于高阶函数的 rethrows
如果您的函数接受一个可能抛出错误的闭包,请使用 rethrows。如果闭包不抛出错误,这允许您不将自己的函数标记为 throws:
func process(_ closure: () throws -> Void) rethrows { try closure()}
// 使用非 throws 闭包调用——不需要 do-catchprocess { print("没有错误") }
// 使用 throws 闭包调用——需要处理try process { throw NetworkError.noData }4.2. deferred:保证的清理
defer 块在函数结束(成功或错误)时执行。非常适合关闭文件或释放资源。