从 Swift 的第一个版本开始,我们就可以在单表达式闭包中省略return 关键字,比如这个闭包,它试图将String 数组中的每个元素转换为一个等价的Int :
let strings = ["1", "2", "3"]
let ints = strings.compactMap { string in
Int(string)
}
然而,如果一个给定的闭包包含多个表达式,那么return 关键字就不能被省略,即使该闭包不包含任何条件或独立的代码分支。因此,传递到下面对map 的调用中的闭包需要明确地将其最后一个表达式标记为其返回值:
class GameController {
private(set) var players = [Player]()
func reviveAllPlayers() {
players = players.map { player in
var player = player
player.isActive = true
player.hitPoints = 100
return player
}
}
}
在 Swift 5.1 中,上述行为被扩展到也包括函数和计算属性,同时保持完全相同的规则。所以现在,当写一个只包含一个计算返回值的表达式的函数时,那么我们也可以省略return 关键字--像这样:
extension GameController {
func playersQualifiedForNextLevel() -> [Player] {
players.filter { player in
player.isActive && player.score > 1000
}
}
}
上面我们实际上是省略了两个返回关键字--包括我们函数中的顶级关键字,以及传递给filter 的闭包关键字。
同样地,当实现一个简单地返回单个表达式结果的计算属性时,return 关键字现在也可以被省略--这通常使得在一行代码中实现整个计算属性成为可能,如果那是我们想做的事情。
extension GameController {
var gameCanBeStarted: Bool { players.count > 1 }
}
但值得注意的是,所有这些功能都是完全可选的。如果我们想的话,我们可以修改到目前为止我们所看到的每一个例子,转而总是使用显式的return 关键字,而且一切都会保持完全相同的工作方式。
然而,当我们开始采用SwiftUI时,有一件事可能会使上述行为变得稍微混乱。当使用闭包来构建 SwiftUI 容器的主体时,最初可能会觉得我们实际上能够省略return 关键字,甚至在包含多个表达式或代码路径的闭包中也是如此--比如这样:
struct RootView: View {
@ObservedObject var loginController: LoginController
var body: some View {
NavigationView {
if let user = loginController.loggedInUser {
HomeView(user: user)
} else {
LoginView(handler: loginController.performLogin)
}
}
}
}
然而,上述闭包实际上并没有使用多个隐式返回,而是由 SwiftUI 的ViewBuilder 处理--它是一个函数/结果生成器,可以接收我们的每个视图表达式并将其合并为一个返回类型。
为了说明这一点,让我们看看如果我们把上述闭包变成一个方法会发生什么:
struct RootView: View {
@ObservedObject var loginController: LoginController
var body: some View {
NavigationView(content: makeContent)
}
private func makeContent() -> some View {
if let user = loginController.loggedInUser {
HomeView(user: user)
} else {
LoginView(handler: loginController.performLogin)
}
}
}
当试图编译上述代码时,我们现在会得到以下构建错误:
Function declares an opaque return type, but has no return
statements in its body from which to infer an underlying type.
为了解决这个问题,我们必须用SwiftUI标记许多闭包参数的同样的@ViewBuilder 属性来标记我们的新makeContent 方法--这再次使我们有可能在没有任何return 关键词的情况下声明多个表达式:
struct RootView: View {
@ObservedObject var loginController: LoginController
var body: some View {
NavigationView(content: makeContent)
}
@ViewBuilder private func makeContent() -> some View {
if let user = loginController.loggedInUser {
HomeView(user: user)
} else {
LoginView(handler: loginController.performLogin)
}
}
}
所以,总结一下:
- Swift的
return关键字总是可以在所有单表达式闭包、函数和计算属性中被省略掉。但是,如果我们愿意,我们仍然可以在这些语境中使用显式返回。 - 只要一个闭包、函数或计算属性包含多个顶层表达式,那么我们就需要用
return关键字明确地标记我们的返回值表达式。 - 当然,实际上不返回任何东西的函数或闭包(或者,从技术上讲,返回
Void)根本不需要包含任何return关键字,除非我们想手动退出该范围 - 例如通过执行 早期返回。 - 在这种情况下,SwiftUI有点特殊,因为它不依赖于隐式返回(大部分情况下),而是使用它的
ViewBuilder,将我们在一个给定容器中的所有表达式合并成一个单一的视图值。