何时可以省略Swift的return关键字?

从第一版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
        }
    }
}

在上面,我们实际上省略了两个return关键字——都是函数内的顶级关键字,以及传递给filter的闭包关键字。

同样,当实现一个计算属性,只返回一个表达式的结果,然后return关键字也可以被忽略——如果我们想这样做的话,这通常会使一个完整的计算属性实现在一行代码中是可能的:

extension GameController {
    var gameCanBeStarted: Bool { players.count > 1 }
}

不过,值得注意的是,所有这些特性都是可选的。如果愿意,我们可以修改到目前为止所看到的每个示例,而不是始终使用显式return关键字,这样一切将保持完全相同的工作方式。

然而,当我们开始采用SwiftUI时,可能会使上述行为稍微令人困惑。当使用闭包来构造swifitui容器体时,最初看起来我们实际上可以忽略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)
        }
    }
}

当尝试编译上述代码时,我们现在会得到以下构建错误:

函数声明不透明的返回类型,但没有返回 语句,从中推断底层类型。

为了解决这个问题,我们必须用与SwiftUI标记许多闭包参数相同的@ViewBuilder属性来标记新的makeContent方法——这再次使我们可以声明多个表达式而不返回任何关键字:

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

总结一下:

  1. Swift的return关键字在所有单表达式闭包、函数和计算属性中都可以被省略。但是,如果我们愿意,我们仍然可以在这些上下文中使用显式返回。

  2. 每当闭包、函数或computed属性包含多个顶级表达式时,我们需要用return关键字显式地标记返回值表达式。

  3. 当然,实际上不返回任何东西(或者,从技术上来说,返回Void)的函数或闭包根本不需要包含任何return关键字,除非我们想手动退出该作用域——例如通过执行早期返回。

  4. SwiftUI在这个上下文中有点特殊,因为它不依赖隐式返回(在大多数情况下),而是使用它的ViewBuilder将给定容器中的所有表达式组合成一个视图值。