我想从一些“深度”函数中抛出一个异常,所以它会冒泡到另一个函数,我想抓住它。
f1
调用f2
调用f3
调用... fN
可能会抛出错误
我想从f1
那里得到错误。
我已经读过在Swift中我必须使用throws
声明所有方法,并且还使用try
调用它们。
但那很烦人:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
do {
try f2()
} catch {
print("recovered")
}
}
func f2() throws {
try f3()
}
func f3() throws {
try f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
是不是与Java中的RuntimeException
有类似的概念,throws
在调用链中一直没有泄漏?
Swift中的错误处理机制不涉及提升未检查(运行时)异常。相反,需要显式错误处理。 Swift肯定不是最近设计的这种设计语言 - 例如Rust和Go也以自己的方式也需要明确描述代码中的错误路径。在Objective-C中,存在未经检查的异常特征,但主要用于传递程序员错误,除了一些关键的Cocoa类,例如NSFileHandle
,它往往会把人们赶出去。
从技术上讲,你有能力使用NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
在Swift中引发Objective-C异常,正如in this excellent answer对this question的解释,可以说是你问题的副本。你真的不应该提出NSExceptions(尤其是因为你没有Swift中可用的Objective-C异常捕获语言功能)。
他们为什么选择这种设计? Apple的"Error Handling in Swift 2.0"文件清楚地解释了理由。引自那里:
这种方法与使用NSError约定在Objective-C中手动实现的错误处理模型非常相似。值得注意的是,该方法保留了此约定的这些优点:
- 方法是否产生错误(或不产生错误)是其API合同的明确部分。
- 方法默认不产生错误,除非明确标记它们。
- 函数内的控制流程仍然是显式的:维护者可以确切地确定哪些语句可以产生错误,并且简单的检查揭示了函数如何对错误做出反应。
- 抛出错误提供了与分配错误并返回错误类似的性能 - 它不是一个昂贵的,基于表的堆栈展开过程。使用标准NSError模式的Cocoa API可以自动导入到这个世界中。其他常见模式(例如CFError,errno)可以在Swift的未来版本中添加到模型中。
[…]
至于基本语法,我们决定坚持使用熟悉的异常处理语言。 [...]总的来说,此提案中的错误传播与异常处理中的错误传播一样,人们不可避免地要进行连接。
对的,这是可能的!
使用:fatalError("your message here")
抛出运行时异常
要详细说明Максим Мартынов's answer,Swift有三种方法可以抛出未声明的,无法捕获的错误(但如果你想在Swift的标准库之外冒险,可以使用other approaches are possible)。这些基于3个优化级别:
-Onone
:没有优化;调试构建-O
:正常优化;发布版本-O SWIFT_DISABLE_SAFETY_CHECKS
:未经检查的优化;极其优化的构建assertionFailure(_:)
在进行调试测试时写下这一行并点击你认为不应该被击中的一行。这些在非调试版本中被删除,因此您必须假设它们永远不会在生产应用程序中被命中。
这有一个名为assert(_:_:)
的姐妹函数,它允许您在运行时断言条件是否为真。当你知道情况总是糟糕的时候,assertionFailure(_:)
就是你所写的,但不要认为这会严重损害生产代码。
if color.red > 0 {
assertionFailure("The UI should have guaranteed the red level stays at 0")
color = NSColor(red: 0, green: color.green, blue: color.blue)
}
preconditionFailure(_:)
当你确定你所描述的某些条件(在文档中等)没有得到满足时写下这一行。这有点像assertionFailure(_:)
,但在发布版本和调试版本中。
像assertionFailure(_:)
一样,这个有一个名为precondition(_:_:)
的姐妹函数,它可以让你在运行时决定是否满足前提条件。 preconditionFailure(_:)
本质上就是这样,但假设一旦程序进入该行,就永远不会满足前提条件。
guard index >= 0 else {
preconditionFailure("You passed a negative number as an array index")
return nil
}
请注意,在极其优化的构建中,如果此行被命中,则不会定义会发生什么!因此,如果您不希望自己的应用程序出现问题,请确保错误状态可以处理。
fatalError(_:)
用作最后的手段。当其他每一次挽救这一天的尝试失败时,这就是你的核武器。打印传递给它的消息(连同文件和行号)后,程序停止运行。
一旦程序到达此行,此行始终运行,程序永远不会继续。即使在极其优化的构建中也是如此。
#if arch(arm) || arch(arm64)
fatalError("This app cannot run on this processor")
#endif
是不是与Java中的
RuntimeException
有类似的概念,throws
在调用链中一直没有泄漏?
Swift确实有错误处理,它不会在编译时传播。
但是,在我讨论这些之前,我必须说你指出的那个,你使用语言的do...catch
,try
,throw
和throws
关键字/功能来处理错误,是目前最安全和最受欢迎的。这样可以确保每次抛出或捕获错误,都可以正确处理。这完全消除了意外错误,使所有代码更安全和可预测。由于固有的编译和运行时安全性,您应该尽可能地使用它。
func loadPreferences() throws -> Data {
return try Data(contentsOf: preferencesResourceUrl, options: [.mappedIfSafe, .uncached])
}
func start() {
do {
self.preferences = try loadPreferences()
}
catch {
print("Failed to load preferences", error)
assertionFailure()
}
}
guard let fileSizeInBytes = try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64 else {
assertionFailure("Couldn't get file size")
return false
}
还有assertion
s,precondition
s和fatalError
s,I described in detail in my answer from October of 2017。编译器提供对这些的合理处理,例如确保在适当时放置和省略返回语句和其他控制流。
如果您的目标是立即停止该计划,exit
就在这个家庭中。
如果你在Swift外面冒险进入更广泛的生态系统,你也会看到Objective-C的NSException
。如你所愿,Swift可以抛出这个,而不使用任何防御它的语言功能。确保记录下来!但是,单凭Swift无法抓住这个!您可以编写a thin Objective-C wrapper,让您在Swift世界中与它进行交互。
func silentButDeadly() {
// ... some operations ...
guard !shouldThrow else {
NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %@", arguments: withVaList([problematicValue], {$0}))
return
}
// ... some operations ...
}
func devilMayCare() {
// ... some operations ...
silentButDeadly()
// ... some operations ...
}
func moreCautious() {
do {
try ObjC.catchException {
devilMayCare()
}
}
catch {
print("An NSException was thrown:", error)
assertionFailure()
}
}
当然,如果你在Unix环境中编写Swift,你仍然可以访问Unix interrupts这个可怕的世界。 You can use Grand Central Dispatch to both throw and catch these。并且,正如您所希望的那样,编译器无法防止它们被抛出。
import Dispatch // or Foundation
signal(SIGINT, SIG_IGN) // // Make sure the signal does not terminate the application.
let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
sigintSource.setEventHandler {
print("Got SIGINT")
// ...
exit(0)
}
sigintSource.resume()
如果您的目标是exit
并阅读其代码,那么trap it就属于这个家庭。