是否可以在Swift中抛出“RuntimeException”而不声明它? assertionFailure(_ :) preconditionFailure(_ :) fatalError(_ :)

问题描述 投票:15回答:3

我想从一些“深度”函数中抛出一个异常,所以它会冒泡到另一个函数,我想抓住它。

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 exception exception-handling runtimeexception
3个回答
5
投票

Swift中的错误处理机制不涉及提升未检查(运行时)异常。相反,需要显式错误处理。 Swift肯定不是最近设计的这种设计语言 - 例如RustGo也以自己的方式也需要明确描述代码中的错误路径。在Objective-C中,存在未经检查的异常特征,但主要用于传递程序员错误,除了一些关键的Cocoa类,例如NSFileHandle,它往往会把人们赶出去。

从技术上讲,你有能力使用NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()在Swift中引发Objective-C异常,正如in this excellent answerthis question的解释,可以说是你问题的副本。你真的不应该提出NSExceptions(尤其是因为你没有Swift中可用的Objective-C异常捕获语言功能)。

他们为什么选择这种设计? Apple的"Error Handling in Swift 2.0"文件清楚地解释了理由。引自那里:

这种方法与使用NSError约定在Objective-C中手动实现的错误处理模型非常相似。值得注意的是,该方法保留了此约定的这些优点:

  • 方法是否产生错误(或不产生错误)是其API合同的明确部分。
  • 方法默认不产生错误,除非明确标记它们。
  • 函数内的控制流程仍然是显式的:维护者可以确切地确定哪些语句可以产生错误,并且简单的检查揭示了函数如何对错误做出反应。
  • 抛出错误提供了与分配错误并返回错误类似的性能 - 它不是一个昂贵的,基于表的堆栈展开过程。使用标准NSError模式的Cocoa API可以自动导入到这个世界中。其他常见模式(例如CFError,errno)可以在Swift的未来版本中添加到模型中。

[…]

至于基本语法,我们决定坚持使用熟悉的异常处理语言。 [...]总的来说,此提案中的错误传播与异常处理中的错误传播一样,人们不可避免地要进行连接。


34
投票

对的,这是可能的!

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


8
投票

要详细说明Максим Мартынов's answer,Swift有三种方法可以抛出未声明的,无法捕获的错误(但如果你想在Swift的标准库之外冒险,可以使用other approaches are possible)。这些基于3个优化级别:

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

1. assertionFailure(_:)

在进行调试测试时写下这一行并点击你认为不应该被击中的一行。这些在非调试版本中被删除,因此您必须假设它们永远不会在生产应用程序中被命中。

这有一个名为assert(_:_:)的姐妹函数,它允许您在运行时断言条件是否为真。当你知道情况总是糟糕的时候,assertionFailure(_:)就是你所写的,但不要认为这会严重损害生产代码。

Usage:

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(_:)本质上就是这样,但假设一旦程序进入该行,就永远不会满足前提条件。

Usage:

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

请注意,在极其优化的构建中,如果此行被命中,则不会定义会发生什么!因此,如果您不希望自己的应用程序出现问题,请确保错误状态可以处理。

3. fatalError(_:)

用作最后的手段。当其他每一次挽救这一天的尝试失败时,这就是你的核武器。打印传递给它的消息(连同文件和行号)后,程序停止运行。

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

Usage:

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

进一步阅读:Swift Assertions by Andy Bargh


0
投票

是不是与Java中的RuntimeException有类似的概念,throws在调用链中一直没有泄漏?

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

但是,在我讨论这些之前,我必须说你指出的那个,你使用语言的do...catchtrythrowthrows关键字/功能来处理错误,是目前最安全和最受欢迎的。这样可以确保每次抛出或捕获错误,都可以正确处理。这完全消除了意外错误,使所有代码更安全和可预测。由于固有的编译和运行时安全性,您应该尽可能地使用它。

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
}

还有assertions,preconditions和fatalErrors,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就属于这个家庭。

© www.soinside.com 2019 - 2024. All rights reserved.