是否可以在不声明的情况下在 Swift 中抛出 "RuntimeException"?

Is it possible to throw a "RuntimeException" in Swift without declaring it?

我想从某个 "deep" 函数中抛出异常,因此它冒泡到另一个函数,我想在其中捕获它。

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 and Go 也以他们自己的方式还需要明确描述代码中的错误路径。在 Objective-C 中存在未经检查的异常功能,但主要仅用于传达程序员的错误,除了一些关键的 Cocoa 类 值得注意的例外,例如 NSFileHandle抓人。

从技术上讲,您确实可以使用 NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise() 在 Swift 中引发 Objective-C 异常,如 in this excellent answer to this question 所述,可以说是您的问题的重复。不过你真的不应该引发 NSExceptions(尤其是因为你在 Swift 中没有可用的 Objective-C 异常捕获语言功能)。

他们为什么采用这种设计? Apple 的 "Error Handling in Swift 2.0" 文档清楚地解释了基本原理。从那里引用:

This approach […] is very similar to the error handling model manually implemented in Objective-C with the NSError convention. Notably, the approach preserves these advantages of this convention:

  • Whether a method produces an error (or not) is an explicit part of its API contract.
  • Methods default to not producing errors unless they are explicitly marked.
  • The control flow within a function is still mostly explicit: a maintainer can tell exactly which statements can produce an error, and a simple inspection reveals how the function reacts to the error.
  • Throwing an error provides similar performance to allocating an error and returning it – it isn’t an expensive, table-based stack unwinding process. Cocoa APIs using standard NSError patterns can be imported into this world automatically. Other common patterns (e.g. CFError, errno) can be added to the model in future versions of Swift.

[…]

As to basic syntax, we decided to stick with the familiar language of exception handling. […] by and large, error propagation in this proposal works like it does in exception handling, and people are inevitably going to make the connection.

是的,有可能!

使用:fatalError("your message here") 抛出运行时异常

如果您想在 Swift 的标准库之外冒险,请详细说明 , Swift has 3 ways to do throw undeclared, uncatchable errors (but 。这些基于 3 个优化级别:

  1. -Onone:没有优化; 调试构建
  2. -O:正常优化; 发布构建
  3. -O SWIFT_DISABLE_SAFETY_CHECKS:未经检查的优化; 极度优化构建

1。 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)
}

2。 preconditionFailure(_:)

当您确定您描述的某些条件(在文档等中)不满足时,写下这一行。 这类似于 assertionFailure(_:),但在发布版本和调试版本中。

assertionFailure(_:) 一样,它有一个名为 precondition(_:_:) 的姊妹函数,可让您在运行时决定是否满足先决条件。 preconditionFailure(_:) 本质上就是这样,但是假设一旦程序到达该行就永远不会满足前提条件。

用法:

guard index >= 0 else {
    preconditionFailure("You passed a negative number as an array index")
    return nil
}

请注意,在极度优化的构建中,未定义如果命中此行会发生什么!因此,如果您不希望您的应用程序假发它可能曾经打过这个,然后确保错误状态是可处理的。

3。 fatalError(_:)

作为最后的手段使用。当所有其他挽救局面的尝试都失败时,这就是你的核武器。在打印您传递给它的消息(连同文件和行号)后,程序停止运行。

一旦程序到达这一行,这一行就一直运行,程序就永远不会继续。 即使在极度优化的构建中也是如此。

用法:

#if arch(arm) || arch(arm64)
    fatalError("This app cannot run on this processor")
#endif

进一步阅读:Swift Assertions by Andy Bargh

Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?

Swift 确实有错误处理,在编译时不会传播。

但是,在我讨论这些之前,我必须说,您指出的那个,您在其中使用了语言的 do...catchtrythrowthrows keywords/features 来处理错误,是迄今为止最安全和最优选的。这确保每次可能抛出或捕获错误时,它都会得到正确处理。这完全消除了意外错误,使所有代码更加安全和可预测。由于固有的编译和 运行 时间安全性,您应该尽可能使用它。

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
}

可能使 Swift 的编译器静音的最简单方法是使用 try! - 这将允许您使用本机 Swift 错误,但也可以忽略它们。

这是您的示例代码的样子:

enum MyErrorType : ErrorType {
    case SomeError
}

func f1() {
    f2()
}

func f2() {
    f3()
}

func f3() {
    try! f4()
}

...

func fN() throws {
    if (someCondition) {
      throw MyErrorType.SomeError
    }
}

显然,这存在不允许您捕获这些错误的问题,因此如果您想要一个静默错误,您可以捕获,继续阅读。


还有assertionpreconditionfatalError,其中。编译器对这些提供了合理的处理,例如确保在适当的时候放置和省略 return 语句和其他控制流。像try!,不过,这些是抓不到的

如果您的目标是立即停止该程序,

exit 就在这个家庭中。


如果您在 Swift 之外冒险进入更广泛的 Apple 生态系统(也就是说,如果您 正在 在 Apple 平台上编写 Swift,您也查看 Objective-C 的 NSException. As you desire, this can be thrown by Swift without using any language features guarding against that. Make sure you document that! However, this cannot be caught by Swift alone! You can write ,让您可以在 Swift 世界中与它互动。

func silentButDeadly() {
    // ... some operations ...

    guard !shouldThrow else {
        NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %@", arguments: withVaList([problematicValue], {[=13=]}))
        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. 的恐怖世界。而且,如您所愿,编译器无法防止它们被抛出。

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 并阅读它的代码,那么

exit 就属于这个系列。