【问题标题】:Exit early using `guard` without typing `return` - only possible using `Never`?使用`guard`提早退出而不输入`return` - 只能使用`Never`?
【发布时间】:2018-09-20 07:14:23
【问题描述】:

Swift 的guard 声明非常适合提前退出。在某些情况下,除了使用return 退出之外,我们可能还想执行一个调用。

final class AppCoordinator {
    func showApplePaySplash() -> Void { /* some presentation logic */ }
}

final class OnboardingCoordinator {
    init(settings: Settings, parent: AppCoordinator) {
        // This code should probably use a `switch` statement and not `guard`, but I am curious about this
        guard settings.hasSeenApplePaySplash else { 
            parent.showApplePaySplash() // method returning `Void`
            return
        }
        // Some more logic...
    }
}

我很好奇的是是否可以缩短语法:

guard settings.hasSeenApplePaySplash else { 
    parent.showApplePaySplash()
    return
}

由于这是在 init 中,我们不能写:

guard settings.hasSeenApplePaySplash else { 
    return parent.showApplePaySplash() // compilation error: `'nil' is the only return value permitted in an initializer`
}

我们当然可以把四行改成这个oneliner:

guard settings.hasSeenApplePaySplash else { parent.showApplePaySplash(); return }

恕我直言,读起来非常好。但我仍然想摆脱那个return(因为我很好奇它是否可能。不需要告诉我:“只使用return man”)。

在另一种情况下,我们想guard 反对一些未定义的不良行为/状态:

guard index < myArray.count else { fatalError("Array out of bounds exception, did you think about X, Y, Z?") }

我们不需要写return,因为fatalError方法返回了特定类型Never

注意:此处以下的代码只是出于好奇而进行的实验,因为它是糟糕的 Swift 代码:

如果我们可以更改以下签名:

func showApplePaySplash() -> Void

使用Never,像这样:

func showApplePaySplash() -> Never

然后我们可以替换:

guard settings.hasSeenApplePaySplash else { parent.showApplePaySplash(); return }

这就是我很好奇的,再一次,不是首选或认可: 只需:

guard settings.hasSeenApplePaySplash else { parent.showApplePaySplash() }

Never 没有任何初始化程序。似乎创建Never 的唯一可能性是使用诸如fatalError 之类的方法来创建崩溃。

我通过@guy-daher 找到了这个execellent SO answer - 可以替换fatalError,从而可以在测试中“捕获”它。但是它使用了waitForExpectations(timeout: 0.1),这在测试套件之外是不可能的?

所以Never 在这里可能没有帮助。 Pre Swift 4 (pre Swift 3?) 有一个名为@noreturn 的函数注释,它似乎可以提供帮助?

有什么方法可以实现吗? :)

【问题讨论】:

  • 从技术上讲,您可以使用 guard 提前退出,而无需使用 return,而使用 throw... 但我认为这并不是您想要的。不过,我不太确定您要查找的 是什么,所以 ::shrug::
  • @noreturn 是 Never 的前一个。你是对的,Never 在这里帮不了你,因为Never 函数不会让你退出范围。
  • 我看到你正在使用Coordinator 模式。我见过的大多数示例都使用start() 函数。尝试将您的 showApplePaySplash 逻辑放在那里,并且仅使用 init 设置对 VC 或类似内容的引用。
  • 你想要做的是一个明确的非 Swift 目标。您必须显式返回(或调用显式永远不会返回的东西)是有意的。因此,即使您想出了一些允许它的聪明方法,它也将是糟糕的 Swift。 (因为这只是好奇,不,这是不可能的,如果是的话,它可能会作为一个意外功能被删除。)请参阅常见拒绝更改列表中的“将保护重命名为除非”,它解释了必须在guard 块:github.com/apple/swift-evolution/blob/master/…
  • @koen 这只是一些示例代码。我将 RxFlow 用于协调器模式,这实际上是一个“步进器”,然后我很好奇 ; return 是否可以被删除。

标签: swift swift4 guard


【解决方案1】:

Never is the new @noreturn@noreturn 意味着在函数返回后执行实际上不可能继续。 Never 的重点恰恰在于它是一种无人居住的类型,不可能创建它的实例。

Never(和之前的@noreturn)对编译器有特殊的意义:当你调用一个“从不返回”的函数时,编译器不必假设在函数调用,并且可以在假设代码永远不会执行的情况下执行优化。在实践中,LLVM 在调用后添加一个陷阱指令(如 x86 上的 ud2)以确保如果函数确实返回则程序崩溃

您可以执行以下操作之一:

  • init?更改为init(...) throws,使parent.showApplePaySplash()返回Error,并在guard子句中使用throw parent.showApplePaySplash()
  • init 和平相处并返回nil
  • init设为私有,并使用class func来创建您的对象(恕我直言,这是最好的风格,因为我的理念是初始化程序通常只应确保对象是自洽的,并且与应该在另一个级别上确保其他状态)。

【讨论】:

  • 绝对同意最后一个最好。仅仅创建一个对象不应该产生可见的副作用;肯定不会仅仅通过调用init 来造成 UI 影响。
  • 这是 RxFlows "Stepper"s 使用的模式:github.com/RxSwiftCommunity/RxFlow/blob/develop/RxFlowDemo/… 但是是的,应该可以更改。我可能会做公关! :)
【解决方案2】:

为什么不使用 defer 来指定退出清理代码?

final class OnboardingCoordinator {
    init(settings: Settings, parent: AppCoordinator) {
        defer {
            parent.showApplePaySplash() // method returning `Void`
        }
        guard settings.hasSeenApplePaySplash else { 
            return
        }
        // Some more logic...
    }
}

【讨论】:

  • guard 语句的重点是在不满足条件时提前退出作用域。如果之后有要采取的行动,使用常规的if-else 语句可能会更好。
  • 同意,但在某些情况下,您希望您的保护语句退出一个函数,但您有“清理”代码要在 ALL 从函数返回之前运行。这就是 defer 子句的用途,也是我建议它的原因。
  • 因为它是一个简化版本...我希望有几个步骤的控制流,一些枚举可能是首选和切换大小写,但这不是我感兴趣的。我很好奇从语言的角度来看是否可行。
猜你喜欢
  • 1970-01-01
  • 2020-08-09
  • 1970-01-01
  • 1970-01-01
  • 2020-02-20
  • 2021-08-24
  • 2022-12-13
  • 1970-01-01
  • 2017-01-18
相关资源
最近更新 更多