Do-Catch:什么是它,Swift中的结构及错误处理

作者: IT Sectr 发布日期: 2026-05-26 阅读时间: 8 分钟

Do-Catch — Swift语言中用于错误处理的构造,它捕获通过throw抛出的异常并执行相应的代码块。与其他语言中传统的try-catch不同,Swift要求使用throws修饰符显式标记可能抛出错误的函数。根据Swift.org, 2026,Do-Catch是Swift中主要的错误处理机制,在类型级别上确保安全。

要点

  • Do-Catch — Swift构造,从带有try和throw的块中捕获错误。
  • throws — 函数标记,表示该函数可能抛出错误。
  • try — 在do块内调用throwing函数的关键字。
  • catch — 错误处理块,具有按类型匹配的功能。
  • try?try! — 将错误转换为nil或导致崩溃的替代形式。

什么是Do-Catch?

Do-Catch — 是Swift中的一种构造,由一个do块(在其中执行可能抛出错误的代码)和一个或多个catch块(处理此错误)组成。do块包含标有try的throwing函数调用,catch块按类型匹配错误。

Swift实现了结构化错误处理模型,不同于Objective-C中的异常。在这里,错误不是带有堆栈展开的exception,而是符合Error协议的值。通过throw抛出错误将控制权转移到最近的catch块,而无需堆栈展开的开销。

Swift的关键特性 — 处理的显式性。编译器不允许在没有try、do-catch或替代机制(try?、try!)的情况下调用throwing函数。这消除了错误被忽视的情况。

Swift中的错误模型:Error协议和throwing函数

Swift中错误处理的基础是Error协议。任何符合此协议的类型都可以通过throw抛出。通常,错误被定义为带有Error确认的枚举。

Error协议

Error — 一个空协议(标记)。它不需要实现方法,只需要确认符合性。Swift编译器使用Error进行静态检查:带有throws的函数只能抛出符合Error的值。

Throwing函数

标有throws的函数必须使用try调用。如果在此类函数内部发生throw,控制权将转移到调用方的do-catch。如果错误在调用链中的任何地方都未被处理,则会传播到更高级别。

swift
enum FileError: Error {
    case notFound
    case permissionDenied
    case corrupted(String)
}

func readFile(path: String) throws -> String {
    guard FileManager.default.fileExists(atPath: path) else {
        throw FileError.notFound
    }
    return try String(contentsOfFile: path)
}

使用rethrows传播错误

rethrows — 用于接受throwing闭包作为参数的函数的修饰符。如果传递的闭包不抛出错误,则无需try即可调用rethrows函数。这允许编写与throwing和non-throwing闭包兼容的通用高阶函数。

Do-Catch语法:多个catch块

Swift支持多个catch块,按错误类型进行匹配。这类似于Kotlin中的when或按类型的switch:第一个匹配的catch执行,其余被跳过。

按类型匹配

每个catch块可以包含与特定错误类型的匹配模式。Swift按顺序检查块,因此更具体的类型应放在通用类型之前。如果没有模式匹配,则执行无模式的catch块。

处理多种类型

类型安全 Do-Catch允许单独处理每个错误,或通过pattern-matching中的逗号将多种类型组合在一个块中。这消除了Objective-C中典型的冗长if-else链,其中错误分析通过返回码或带有domain和code检查的NSError进行。

Swift还支持catch where — 带附加条件的匹配。例如,可以根据特定代码或文本过滤错误:catch URLError where error.code == .notConnectedToInternet。这减少了catch块的数量,同时保持处理的精确性。

对于调试throwing函数,Swift提供了错误和异常之间的区别。Swift错误 — 是一个返回的值,不需要堆栈展开。Objective-C异常(@try/@catch)在运行时级别工作,仅用于致命故障。Do-Catch处理的是错误,而不是异常,这使其可预测且高效。

在开发库和SDK时,重要的是要记住throwing函数是公共契约的一部分。错误类型未在签名中指定,因此请通过注释或类似Result的枚举记录函数可能抛出的错误。这有助于API用户编写正确的catch块,而无需查看实现。

异步编程的背景下,使用async/await的Do-Catch解决了回调地狱问题。以前,在Swift中,异步代码中的错误处理需要在completion handler中使用带有Error?检查的嵌套闭包。通过async/await和Do-Catch,异步throwing代码看起来像同步代码,这简化了具有每一步错误处理的复杂请求链的阅读和维护。

swift
do {
    let content = try readFile(path: "/data/config.json")
    process(content)
} catch FileError.notFound {
    createDefaultConfig()
} catch FileError.permissionDenied {
    requestAccess()
} catch FileError.corrupted(let detail) {
    logCorruption(detail)
} catch {
    print("未知错误:\(error)")
}

Try变体:try、try?、try!以及何时使用它们

Swift提供了三种调用throwing函数的变体:trytry?try!。每个变体解决特定任务并有其自身的局限性。

try — 标准调用

try在do块内使用,错误在catch中处理。这是调用throwing函数的主要方式。编译器要求try位于错误可以被捕获的上下文中。

try? — 转换为Optional

try?将throwing函数的结果转换为Optional。如果函数抛出错误,try?返回nil,否则返回带有成功值的Optional。适用于可以忽略错误但需要成功指示符的调用。

try! — 强制执行

try!抑制错误处理。如果throwing函数抛出错误,应用程序将因运行时错误而终止。仅在绝对确定错误不可能时使用try! — 例如,加载内置资源时。

swift
// try? — 忽略错误,在失败时获得nil
if let data = try? Data(contentsOf: url) {
    processData(data)
}

// try! — 仅当错误保证不可能时
let bundled = try! String(contentsOfFile: "Assets/default.txt")

// try — do-catch中的标准选项
do {
    let result = try performNetworkRequest()
    updateUI(result)
} catch {
    showError(error)
}

iOS开发中的Do-Catch:实际示例

Do-Catch在iOS开发中被广泛用于处理系统API。让我们考虑典型场景:文件系统操作、Core Data和网络请求。

文件系统

FileManager的许多方法都是throwing的。Do-Catch可以正确处理文件缺失、资源被占用或权限不足的情况。

使用Codable的网络请求

通过JSONDecoder解析JSON — 一个throwing操作。Do-Catch分别捕获解码错误和网络故障,向用户提供准确的错误文本。

swift
struct User: Codable {
    let id: Int
    let name: String
}

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// 带处理的调用
do {
    let user = try await fetchUser(id: 42)
    showUser(user)
} catch let error as URLError {
    showNetworkAlert(error)
} catch let error as DecodingError {
    showParseError(error)
} catch {
    showGenericError(error)
}

使用Do-Catch时的常见错误

开发人员,尤其是从其他语言过渡的开发人员,在使用Do-Catch时经常犯典型错误。让我们看看最常见的错误。

  • 忘记try — 不带try调用throwing函数会导致编译错误。Swift不允许隐式忽略错误。
  • 空的catch — 没有处理的catch块隐藏了错误。始终记录或处理错误,即使它看起来不可能。
  • 没有匹配的catch — 一个适用于所有错误的通用catch失去了类型安全的优势。通过多个块按类型分隔处理。
  • 过度使用try! — 生产代码中的try!是意外崩溃的来源。仅用于内置资源或测试。

常见问题

Swift中的Do-Catch与其他语言中的try-catch有何不同?

Swift中,错误是符合Error协议的值,而不是带有堆栈展开的exception。Throwing函数必须显式地用throws标记,编译器在调用时要求使用try — 这消除了编译阶段未处理的错误。

Do-Catch可以与async/await一起使用吗?

是的,async函数也可以是throws,do-catch适用于它们。调用async throwing函数需要在do块内使用try await。这是在Swift异步代码中处理错误的标准方式。

如何在没有do-catch的情况下处理错误?

有替代方案:try?将错误转换为nil,try!在错误时导致崩溃,rethrows允许从闭包传播错误。Do-catch仍然是显式处理的主要方式。

一个函数可以抛出不同类型的错误吗?

Swift不类型化抛出的错误 — 任何throwing函数都可以抛出任何符合Error的类型。通过catch中按特定错误类型的匹配来分隔处理。

是否应该将所有错误都包装到自定义类型中?

是的,这是最佳实践。将AppError定义为带有Error确认的枚举,并将系统错误转换为领域错误。这统一了处理并将应用程序层与系统细节隔离。

总结

  • Do-Catch — 用于错误处理的Swift构造,函数上必须有throws标记,调用时必须有try。
  • Swift错误 — 符合Error协议的值,而不是带堆栈展开的异常,这在没有故障时提供零开销。
  • 多个catch允许使用类似于switch的pattern-matching分别处理不同类型的错误。
  • try?将错误转换为nil用于简单场景,try! — 具有崩溃风险的强制调用,仅用于保证成功的操作。
  • 带有async/await的Do-Catch — 使用try await在异步Swift代码中处理错误的标准方式。
  • 避免空的catch和过度使用try! — 这会隐藏错误并导致生产中发生意外故障。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读