什么时候可以省略掉Swift的`return`关键词?

630 阅读4分钟

从 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 ,将我们在一个给定容器中的所有表达式合并成一个单一的视图值。