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()
;
与 - 返回
nil
; init
私有,并使用class func
来创建对象(恕我直言,这是最好的样式,因为我的理念是初始值设定项通常只应确保对象是自洽的,并且应该在另一个级别确保与其他状态的一致性)。
init
和平相处,并为什么不使用 defer 来指定您的退出清理代码?
final class OnboardingCoordinator {
init(settings: Settings, parent: AppCoordinator) {
defer {
parent.showApplePaySplash() // method returning `Void`
}
guard settings.hasSeenApplePaySplash else {
return
}
// Some more logic...
}
}