【问题标题】:When and how to use @noreturn attribute in Swift?何时以及如何在 Swift 中使用 @noreturn 属性?
【发布时间】:2016-11-01 02:28:29
【问题描述】:

我在guard-else 流的上下文中读取了关键字else 后面的花括号括起来的代码块,必须调用标记为noreturn 属性的函数或使用returnbreak 传输控制权, continuethrow.

后半部分比较清楚,前半部分不太明白。

首先,任何函数都会返回一些东西(至少是一个空元组),即使你没有声明任何返回类型。其次,我们什么时候可以使用noreturn 函数?建议某些核心内置方法的文档是否标有noreturn

guard 语句的 else 子句是必需的,并且必须调用 用 noreturn 属性或传输程序标记的函数 使用其中之一控制守卫语句的封闭范围之外 以下陈述:

return

break

continue

throw

这里是source

【问题讨论】:

标签: swift noreturn


【解决方案1】:

例如,考虑在结果类型中返回值或错误的异步操作。我们通常这样写。

enum Result<Value, Error> {
    case success(Value)
    case failure(Error)
}

func fetch(_ request: URLRequest,
          completion: (Result<(URLResponse, Data), Error>) -> Void) {
    // ...
}

现在,如果你知道一个函数总是返回值,从不出错,那么我们可以这样写:

func alwaysSucceeds(_ completion: (Result<String, Never>) -> Void) {
    completion(.success("yes!"))
}

当编译器看到 Never 时,它不会强迫你将所有的 switch 用例写得详尽,所以你可以跳过 .failure,如下所示。

alwaysSucceeds { (result) in
    switch result {
    case .success(let string):
        print(string)
    }
}

参考:https://nshipster.com/never/

【讨论】:

    【解决方案2】:

    首先,任何函数都会返回一些东西(至少是一个空元组),即使你没有声明任何返回类型。

    (@noreturn 已过时;请参阅下面的 Swift 3 更新。) 不,有些函数会立即终止进程 并返回给调用者。这些在 Swift 中标记 用@noreturn,比如

    @noreturn public func fatalError(@autoclosure message: () -> String = default, file: StaticString = #file, line: UInt = #line)
    @noreturn public func preconditionFailure(@autoclosure message: () -> String = default, file: StaticString = #file, line: UInt = #line)
    @noreturn public func abort()
    @noreturn public func exit(_: Int32)
    

    可能还有更多。

    (备注:类似的注解存在于其他编程语言中 或编译器,例如 C++11 中的 [[noreturn]],作为 GCC 扩展的 __attribute__((noreturn)),或用于 Clang 编译器。)

    你可以用@noreturn标记你自己的函数,如果它也终止了 该过程无条件,例如通过调用内置函数之一,例如

    @noreturn func myFatalError() {
        // Do something else and then ...
        fatalError("Something went wrong!")
    }
    

    现在您可以在 guard 语句的 else 子句中使用您的函数:

    guard let n = Int("1234") else { myFatalError() }
    

    @noreturn 函数也可用于标记“不应该 发生”并表示编程错误。一个简单的例子 (摘自Missing return UITableViewCell):

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell: MyTableViewCell
    
        switch (indexPath.row) {
        case 0:
            cell = tableView.dequeueReusableCellWithIdentifier("cell0", forIndexPath: indexPath) as! MyTableViewCell
            cell.backgroundColor = UIColor.greenColor()
        case 1:
            cell = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! MyTableViewCell
            cell.backgroundColor = UIColor.redColor()
        default:
            myFatalError()
        }
        // Setup other cell properties ...
        return cell
    }
    

    如果没有将myFatalError() 标记为@noreturn,编译器将 抱怨默认情况下缺少返回。


    更新: 在 Swift 3 (Xcode 8 beta 6) 中,@noreturn 属性 已经被 Never 返回类型替换了,所以上面的例子 现在应该写成

    func myFatalError() -> Never  {
        // Do something else and then ...
        fatalError("Something went wrong!")
    }
    

    【讨论】:

    • 还有UIApplicationMain 永远不会返回(尽管它没有被标记,可能是因为向后兼容)。这是 Obj-C 中的一个问题,编译器无法识别像 [NSException raise] 这样的调用,并且在它们之后仍然必须有一个 return
    • @Sulthan:可能。而且你不会在保护语句中调用 UIApplicationMain() :)
    • @MartinR:我知道我可以将它用于自定义功能,我在发布之前尝试过 :-) 我的问题是:开发人员以这种方式标记功能是否有意义?我正在寻找一个用例:-)
    • @IanBell:用例是您可以在保护语句的 else 子句中使用自定义函数,并且编译器知道它永远不会返回。我添加了另一个示例。如果没有 @noreturn 属性,这两个示例都无法编译。 – 如果你没有无条件终止进程的函数,那么你就不需要它!
    • @IanBell 有很多使用它的理由,你可能正在开发一个多线程应用程序,比如说一个进行网络下载的应用程序,并且在这个下载过程中,你有一些东西让守卫失败.如果函数返回,它将触发完成事件,这是您不希望的,但您还需要暂时保持线程处于活动状态。现在想象几个存在此代码的地方,并且您不想编写重复的 else 条件,您将在所有这些 else 条件上使用此函数,这将允许您的线程在不触发完成事件的情况下终止。
    【解决方案3】:

    简单的游乐场,看看它是如何工作的......

    //: Playground - noun: a place where people can play
    
    import Foundation
    @noreturn func foo() {
        print("foo")
        exit(1)
    }
    
    var i: Int?
    
    guard let i = i else {
        foo()
    }
    
    print("after foo") // this line will never executed
    
    //prints foo and finish
    

    【讨论】:

    • 我知道它是如何工作的,谢谢,但我正在寻找的是一个具体的、真实的例子,在这个例子中这个属性非常有用:-)
    • @IanBell 所以,你看到了这个例子...... :-)。试着想象 foo() 在退出之前做了一些清理,甚至将一些数据传输到服务器或任何你想要的。任务可能非常复杂,这取决于您。
    • 这里也一样...@noreturn(现在“从不”)怎么能有用...真的,在一个真实的例子中...
    • @IanBell 作为另一个让我来到这里的例子是:通常在 guard 语句中,您在失败场景结束时有一个 return...但是这个在我失败的时候,我写了一个preconditionFailure,编译器在返回行上给了我一个警告:“将永远不会被执行”......因为preconditionFailure实际上标志着我的代码执行结束
    猜你喜欢
    • 2016-04-13
    • 1970-01-01
    • 1970-01-01
    • 2017-05-14
    • 2012-05-13
    • 2011-06-07
    • 2016-07-04
    相关资源
    最近更新 更多