Do-Catch — Swift语言中用于错误处理的构造,它捕获通过throw抛出的异常并执行相应的代码块。与其他语言中传统的try-catch不同,Swift要求使用throws修饰符显式标记可能抛出错误的函数。根据Swift.org, 2026,Do-Catch是Swift中主要的错误处理机制,在类型级别上确保安全。
要点
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协议。任何符合此协议的类型都可以通过throw抛出。通常,错误被定义为带有Error确认的枚举。
Error — 一个空协议(标记)。它不需要实现方法,只需要确认符合性。Swift编译器使用Error进行静态检查:带有throws的函数只能抛出符合Error的值。
标有throws的函数必须使用try调用。如果在此类函数内部发生throw,控制权将转移到调用方的do-catch。如果错误在调用链中的任何地方都未被处理,则会传播到更高级别。
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 — 用于接受throwing闭包作为参数的函数的修饰符。如果传递的闭包不抛出错误,则无需try即可调用rethrows函数。这允许编写与throwing和non-throwing闭包兼容的通用高阶函数。
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代码看起来像同步代码,这简化了具有每一步错误处理的复杂请求链的阅读和维护。
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)")
}
Swift提供了三种调用throwing函数的变体:try、try?和try!。每个变体解决特定任务并有其自身的局限性。
try在do块内使用,错误在catch中处理。这是调用throwing函数的主要方式。编译器要求try位于错误可以被捕获的上下文中。
try?将throwing函数的结果转换为Optional。如果函数抛出错误,try?返回nil,否则返回带有成功值的Optional。适用于可以忽略错误但需要成功指示符的调用。
try!抑制错误处理。如果throwing函数抛出错误,应用程序将因运行时错误而终止。仅在绝对确定错误不可能时使用try! — 例如,加载内置资源时。
// 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)
}
Do-Catch在iOS开发中被广泛用于处理系统API。让我们考虑典型场景:文件系统操作、Core Data和网络请求。
FileManager的许多方法都是throwing的。Do-Catch可以正确处理文件缺失、资源被占用或权限不足的情况。
通过JSONDecoder解析JSON — 一个throwing操作。Do-Catch分别捕获解码错误和网络故障,向用户提供准确的错误文本。
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时经常犯典型错误。让我们看看最常见的错误。
常见问题
在Swift中,错误是符合Error协议的值,而不是带有堆栈展开的exception。Throwing函数必须显式地用throws标记,编译器在调用时要求使用try — 这消除了编译阶段未处理的错误。
是的,async函数也可以是throws,do-catch适用于它们。调用async throwing函数需要在do块内使用try await。这是在Swift异步代码中处理错误的标准方式。
有替代方案:try?将错误转换为nil,try!在错误时导致崩溃,rethrows允许从闭包传播错误。Do-catch仍然是显式处理的主要方式。
Swift不类型化抛出的错误 — 任何throwing函数都可以抛出任何符合Error的类型。通过catch中按特定错误类型的匹配来分隔处理。
是的,这是最佳实践。将AppError定义为带有Error确认的枚举,并将系统错误转换为领域错误。这统一了处理并将应用程序层与系统细节隔离。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。