使用"guard"提前退出而不键入"返回" - 只能使用"从不&quo



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`
}

当然,我们可以将四行更改为此单行:

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

恕我直言,这读起来非常好。但我仍然想摆脱这种return(因为我很好奇这是否可能。无需告诉我:"只需使用返回人")。

在另一种情况下,我们希望针对一些未定义的不良行为/状态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 的这个出色的 SO 答案——可以替换fatalError可以在测试中"捕获"它。但它使用的waitForExpectations(timeout: 0.1)在测试套件之外是不可能的?

所以Never在这里可能没有帮助。在 Swift 4 之前(在 Swift 3 之前?)有一个叫做@noreturn的函数注释似乎可以提供帮助?

有什么办法可以做到这一点吗?:)

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

Never(以及之前的@noreturn)对编译器具有特殊意义:当您调用"永不返回"的函数时,编译器不必假设函数调用后存在有效的代码路径,并且可以执行优化,假设代码永远不会被执行。实际上,LLVM 在调用后添加一个陷阱指令(如 x86 上的ud2),以确保如果函数实际返回,程序崩溃

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

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

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

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

最新更新