使用`guard`提前退出而不输入`return` - 只能使用`Never`?

时间:2018-04-10 15:25:57

标签: swift swift4 guard

这个问题是由对Swift的好奇心引起的,它不是像代码一样的生产建议。

对于早期退出,

Swifts 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(因为我很好奇,如果有可能的话。不需要告诉我:“只要使用回归男人”)。

在另一个场景中,我们希望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(前Swift 3?)有一个名为@noreturn的函数注释,看起来它可能有帮助吗?

有没有办法实现这个目标? :)

编辑:

添加免责声明这是一个坏主意,请不要继续用好奇心驱使我的问题。

2 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:2)

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

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

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

  • init?更改为init(...) throws,使parent.showApplePaySplash()返回Error,并在throw parent.showApplePaySplash()子句中使用guard;
  • init特殊的情况下让您感到安宁,并返回nil;
  • 使init变为私有,并使用class func来创建您的对象(这是最好的样式恕我直言,因为我的理念是初始化程序通常应该只确保对象是自洽的,并且应该在另一个层面确保与其他一些国家的一致性。)