---
title: "Swift 5.2到5.4新特性整理"
description: "swift 5.2 到 swift 5.4 新特性大全。"
url: "https://hicc.pro/p/swift/whats-new-in-swift-5-2-to-5-4"
author: "tyler wang"
published: "2021-03-06T08:47:53.000Z"
updated: "2021-07-09T18:04:31.000Z"
category: "Swift"
tags:
  - "Swift Language"
  - "Swift"
image: "https://image.hicc.pro/pro/20210306195233llB_Rfs-7A1mZ3H-lGOV2aWcZ088-gz8_rt9sqrGOgeFuOOu2.jpg"
lang: "zh-CN"
site: "Hipo日志"
---

# Swift 5.2到5.4新特性整理

![深圳东西冲](https://image.hicc.pro/pro/20210306195233llB_Rfs-7A1mZ3H-lGOV2aWcZ088-gz8_rt9sqrGOgeFuOOu2.jpg)

*深圳东西冲*

### Swift 5.4

> **Swift 5.4 需要Xcode 12.5以上**

#### 改善隐式成员语法

[SE-0287](https://github.com/apple/swift-evolution/blob/main/proposals/0287-implicit-member-chains.md)提案改进了Swift使用隐式成员表达式的能力。Swift 5.4之后不但可以使用单个 使用，而且可以链起来使用。

例如我们使用隐式成员：

```swift
struct ContentViewView1: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red)
    }
}
```

我们如果要使用颜色的函数，我们只能用回`Color.red.opacity(0.5)`：

```swift
struct ContentViewView2: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(Color.red.opacity(0.5))
    }
}
```

这点很违反直觉，Swift 5.4之后就不会报错了“Cannot infer contextual base in reference to member 'red'”。可以更简易使用了：

```swift
struct ContentViewView3: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red.opacity(0.5))
    }
}
```

#### 函数支持多个可变参数(variadic parameter)

[SE-0284](https://github.com/apple/swift-evolution/blob/main/proposals/0284-multiple-variadic-parameters.md)能够让函数，下标和初始化器能够支持多个可变参数(需要带上参数label)。在此之前只支持一个。

```swift
func sumarizeGoals(times: Int..., players: String...) {
    let joinedNames = ListFormatter.localizedString(byJoining: players)
    let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init))
    
    print("\(times.count) goals where scord by \(joinedNames) at the follow minutes:\(joinedTimes)")
}
```

调用很方便

```swift
sumarizeGoals(times: 18, 33, 55, 90, players: "Dani", "jamie", "Roy")
// 4 goals where scord by Dani, jamie, and Roy at the follow minutes:18, 33, 55, and 90
```

#### Result Builders

Swift 5.1中非正式的引入了Function Builders。Swift 5.4中[SE-0289](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md)提案把它升级为了Result Builders。

简单来说，**Result Builders最重要功能是可以将我们所需的一系列值一步一步变成一个新值**。这个能力也是SwiftUI view创建系统的核心驱动，例如在`VStack`有一批子view，Swift会在背后将这些view组合成一个内部的`Tupleview`，这样才会被`VStack`真正使用。——Result Builder把一系列view变成了一个view。

举例来说，首先我们有个函数返回一个字符串：

```swift
func makeSenence1() -> String {
    "Why settle for a Duke when you can have a Prince?"
}

print(makeSenence1())
// Why settle for a Duke when you can have a Prince?
```

这点没问题，但是我们如果需要将多个字符串join在一起呢？我们能像SwifUI那么写么？

![Swift 5.2到5.4新特性整理](https://image.hicc.pro/pro/20210311004459Fmwmw5_3GkIMryc5LN_CI1qIjb7ddrkGbk2-HqOK9L8ByInPA.png)

很明显会编译错误，Swift 5.4之后，我们可以创建一个result builder来告诉Swift如何去转换。

```swift
@resultBuilder
struct SimplestringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
}
```

几句代码，可能不好理解：

- `@resultBuilder`属性告知SwiftUI，所装饰的类型应该被当作一个result builder。之前类似的想法是`@_functionBuilder`。但是有下划线，以为着设计来不为常规使用。
- 每个result builder，都必须至少提供一个静态的`buildBlock()`。一般这个函数接受一批数据然后做一些转换。上面例子中，接受0到多个字符串，通过回车符来合并成一个。
- 最后，我们创建的`SimpleStringBuilder`结构体变成了一个result builder。意味着，我们可以在任何字符串join的地方来使用`@simpleStringBuilder`。

可以直接使用SimpleStringBuilder.buildBlock()：

```swift
let joined = SimplestringBuilder.buildBlock(
    "Why settle for a Duke",
    "when you can have",
    "a Prince?"
)

print(joined)
// Why settle for a Duke
// when you can have
// a Prince?
```

当然，我们使用了`@resultBuilder`来装饰了我们的`SimpleStringBuilder`。我们可以如下使用：

```swift
@SimplestringBuilder
func makesentence3() -> String {
    "Why settle for a Duke"
    "when you can have"
    "a Prince?"
}

print(makesentence3())
// Why settle for a Duke
// when you can have
// a Prince?
```

> **注意： 我们不再需要在每个字符串结尾使用逗号**
> `@resultBuilder`自动将`makeSentence()`中的表达式通过`SimpleStringBuilder`来转换成一个字符串。

result builder还可以支持if/else, for循环等表达方式。

```swift
@resultBuilder
struct ConditionalStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
    
    static func buildEither(first component: String) -> String {
        return component
    }
    static func buildEither(second component: String) -> String {
        return component
    }
}
```

可以直接在函数体内使用 if else

```swift
@ConditionalStringBuilder
func makesentence4() -> String {
    "Why settle for a Duke"
    "when you can have"
    if Bool.random() {
        "a Prince?"
    } else {
        "a King?"
    }
}

// Why settle for a Duke
// when you can have
// a King?
```

同样，可以使用buildArray()来支持for循环:

```swift
@resultBuilder
struct ComplexStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
    
    static func buildEither(first component: String) -> String {
        return component
    }
    static func buildEither(second component: String) -> String {
        return component
    }
    
    static func buildArray(_ components: [String]) -> String {
        components.joined(separator: "\n")
    }
}
```

使用起来很酷

```swift
@ComplexStringBuilder
func countDown() -> String {
    for i in (0...10).reversed() {
        "\(i)..."
    }
    
    "Lift off!"
}

print(countDown())
// 10...
// 9...
// 8...
// 7...
// 6...
// 5...
// 4...
// 3...
// 2...
// 1...
// 0...
// Lift off!
```

![Swift result builder 支持不少的炫酷语法](https://image.hicc.pro/pro/20210311013115FtDQXvQTEtxPXK8QCXkwhsYKsN-bMtB0smgjPC68EGIb371Fg.png)

*Swift result builder 支持不少的炫酷语法*

还值得一提的是，Swift 5.4中[result builder也支持作用在属性](https://bugs.swift.org/browse/SR-13188)上， 它会自动让结构体struct的初始化函数应用result builder。

```swift
struct CustomVStack<Content: View>: View {
    @ViewBuilder let content:Content
    
    var body: some View {
        VStack {
            // custom functionality here
            content
        }
    }
}
```

#### 嵌套函数支持重载

SE-10069提案，可以让Swift支持嵌套函数中重载。

```swift
struct Butter { }
struct Flour { }
struct Sugar { }

func makeCookies() {
    func add(item: Butter) {
        print("Adding butter…")
    }

    func add(item: Flour) {
        print("Adding flour…")
    }

    func add(item: Sugar) {
        print("Adding sugar…")
    }

    add(item: Butter())
    add(item: Flour())
    add(item: Sugar())
}
```

在Swift 5.4之前，`add()`方法只有不再`makeCookies()`中才支持重载。

#### Property wrapper支持函数内变量

Property wrapper 从Swift 5.1引入，用来装饰属性，复用代码，在Swift 5.4中也支持函数内变量装饰Property wrapper了。

```swift
@propertyWrapper struct NonNegative<T: Numeric & Comparable> {
    var value: T

    var wrappedValue: T {
        get { value }

        set {
            if newValue < 0 {
                value = 0
            } else {
                value = newValue
            }
        }
    }

    init(wrappedValue: T) {
        if wrappedValue < 0 {
            self.value = 0
        } else {
            self.value = wrappedValue
        }
    }
}
```

在Swift 5.4中函数内使用，就可以避免score不会为负数。

```swift
func playGame() {
    @NonNegative var score = 0

    // player was correct
    score += 4

    // player was correct again
    score += 8

    // player got one wrong
    score -= 15

    // player got another one wrong
    score -= 16

    print(score)
}
```

#### Swift Package支持声明可执行目标

[SE-0294提案](https://github.com/apple/swift-evolution/blob/main/proposals/0294-package-executable-targets.md)可以支持声明Swift Package声明可执行的target。

这点对想使用`@main`属性的情况很有用，因为目前Swift Package包管理会自动寻找main.swift文件，有了这个能力的支持，我们在Package.swift中指定`//swift-tools-version:5.4`就可以移除main.swift文件，使用`@main`的方式了。

---

### Swift 5.3

#### 多模式错误捕捉

[SE-0276提案](https://github.com/apple/swift-evolution/blob/master/proposals/0276-multi-pattern-catch-clauses.md)(Multi-pattern catch clauses)，能够让我们在单个`catch`块中，捕获多个错误，以此来减少重复代码。

例如，我们有下面的错误码。

```swift
enum Temperatureerror: Error {
    case tooCold, tooHot
}
```

如下的业务代码

```swift
func getReactortemperature() -> Int {
    100
}

func checkreactorOperational() throws -> String {
    let temp = getReactortemperature()
    
    if temp < 10 {
        throw Temperatureerror.tooCold
    } else if temp > 90 {
        throw Temperatureerror.tooHot
    } else {
        return "ok"
    }
}
```

Swift 5.2之后，你可以使用逗号，同时处理tooHot和tooCold。

```swift
do {
    let result = try checkreactorOperational()
    print("Result: \(result)")
} catch Temperatureerror.tooHot, Temperatureerror.tooCold {
    print("Shut down the reactor!")
} catch {
    print("An unknown error occurred.")
}
```

#### 多重尾随闭包

SE-0279提案引入了多重尾随闭包，能够让我们更简单的调用有多个闭包参数的函数。

之前我们在SwiftUI中，经常会有如下代码

```swift
struct OldContentView: View {
    @State private var showOptions = false
    
    var body: some View {
        Button(action: {
            self.showOptions.toggle()
        }) {
            Image(systemName: "gear")
        }

    }
}
```

现在可以更简化：

```swift
struct OldContentView: View {
    @State private var showOptions = false
    
    var body: some View {
        Button {
            self.showOptions.toggle()
        } label: {
            Image(systemName: "gear")
        }

    }
}
```

#### 可比较的枚举

[SE-0266](https://github.com/apple/swift-evolution/blob/master/proposals/0266-synthesized-comparable-for-enumerations.md)提案可以让我们给非关联枚举和遵循`comparable`协议的关联值使用`comparable`协议，也就是可以使用`<`,`>`或类似的操作。

```swift
enum Size: Comparable {
    case small
    case medium
    case large
    case extraLarge
}

if Size.small < Size.large {
    print("That shirt is too small")
}
// That shirt is too small
```

对于关联值也是支持的：

```swift
enum WorldCupResult: Comparable {
    case neverWon
    case winner(stars: Int)
}

let americanMen = WorldCupResult.neverWon
let americanWomen = WorldCupResult.winner(stars: 4)
let japaneseMen = WorldCupResult.neverWon
let japaneseWomen = WorldCupResult.winner(stars: 1)

let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]
let sortedByWins = teams.sorted()
print(sortedByWins)
```

注意这里面的**排序默认是根据case的顺序，以及关联值的值大小**，上述，`winner`的会高于`neverWon`，而`winner(stars: 4)` 大于`winner(stars: 1)`。

当然你也可以自定义比较函数：

```swift
enum Size2: Comparable {
    
    case medium
    case large
    case small
    case extraLarge
    
    static func < (lhs: Self, rhs: Self) -> Bool {
        print(lhs)
        print(lhs.hashValue)
        print(rhs.hashValue)
        return lhs.hashValue > rhs.hashValue
    }
}

if Size2.small < Size2.large {
    print("That shirt is too small")
}
// That shirt is too small
```

#### self在很多地方不再必须

[SE-0269](https://github.com/apple/swift-evolution/blob/master/proposals/0269-implicit-self-explicit-capture.md)提案允许我们在很多不需要的地方停止使用`self`。在此之前，我们需要在任何引用self的地方写上`self.`。这样我们就把我们的捕获语义显示化了。然而经常出现的情况是，我们的闭包不会导致引用循环，也就意味着`self`是多余的。

例如在之前

```swift
struct OldContentView: View {
    var body: some View {
        List(1..<5) { number in
            self.cell(for: number)
        }
    }
    
    func cell(for number: Int) -> some View {
        Text("Cell \(number)")
    }
}
```

因为是在struct中调用`self.cell(for:)`，不会导致循环引用。在Swift 5.3中，我们可以这样写：

```swift
struct OldContentView: View {
    var body: some View {
        List(1..<5) { number in
            cell(for: number)
        }
    }
    
    func cell(for number: Int) -> some View {
        Text("Cell \(number)")
    }
}
```

#### 基于类型的程序入口

[SE-0281](https://github.com/apple/swift-evolution/blob/master/proposals/0281-main-attribute.md)提案引入了`@main`的属性来声明程序的入口。

在此之前，我们需要在`main.swfit`文件中，如此启动程序

```swift
struct OldApp {
    func run() {
        print("Running!")
    }
}

let app = OldApp()
app.run()
```

现在我们可以简单如此书写：

```swift
@main
struct NewApp {
    static func main() {
        print("Running!")
    }
}
```

需要注意的是：

- 如果你已经使用了main.swift的方式，你就不能再使用`@main`了
- 只能有一个`@main`
- `@main`只能用在基类中，它不能被任何子类继承。

#### 上下文泛型声明中支持where限制

[SE-0280提案](https://github.com/apple/swift-evolution/blob/master/proposals/0267-where-on-contextually-generic.md)允许在泛型类型和`extension`的函数中使用`where`限制。

例如我们有`Stack`的结构体。

```swift
struct Stack<Element> {
    private var array = [Element]()
    
    mutating func push(_ obj: Element) {
        array.append(obj)
    }
    
    mutating func pop() -> Element? {
        array.popLast()
    }
}
```

Swift 5.3之后，我们可以给stack添加一个sorted()方法，仅仅element遵循Comparable协议。

可以直接在struct中

```swift
struct Stack<Element> {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}
```

也可以在extendion中。

```swift
extension Stack {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}
```

#### Enum cases as protocol witnesses

[SE-0280](https://github.com/apple/swift-evolution/blob/master/proposals/0280-enum-cases-as-protocol-witnesses.md)提案，允许枚举case 更好的匹配协议protocol。

譬如你会写如下的协议和实现来表达默认值。

```swift
protocol Defaultable {
    static var defaultValue: Self { get }
}

// make integers have a default value of 0
extension Int: Defaultable {
    static var defaultValue: Int { 0 }
}

// make arrays have a default of an empty array
extension Array: Defaultable {
    static var defaultValue: Array { [] }
}

// make dictionaries have a default of an empty dictionary
extension Dictionary: Defaultable {
    static var defaultValue: Dictionary { [:] }
}
```

现在同样的事情，也可以用在枚举上。

```swift
enum Padding: Defaultable {
    case pixels(Int)
    case cm(Int)
    case defaultValue
}
```

#### 重新定义didSet

[SE-0268](https://github.com/apple/swift-evolution/blob/master/proposals/0268-didset-semantics.md)提案为更好的效率，调整了`didSet`属性监听的工作方式。简单来说：

- 如果didSet中没有引用`oldValue`，那么就会跳过获取`oldValue`，叫做“simple” `didSet`。
- 如果已经是“simple” `didSet`也没有`willSet`。则直接在赋值的时候直接改值。

当然如果要依赖老方式，可以这么写

```swift
didSet {
    _ = oldValue
}
```

#### 新的Float16类型

SE-0277提案引入了，新的数据类型Float16。这种类型被广泛使用在图形编程和机器学习中。

```swift
let first: Float16 = 5
let second: Float32 = 11
let third: Float64 = 7
let fourth: Float80 = 13
```

#### Swift 包管理改进

这里不再细列，可以参考[SE-0271](https://github.com/apple/swift-evolution/blob/master/proposals/0271-package-manager-resources.md)， [SE-0278](https://github.com/apple/swift-evolution/blob/master/proposals/0278-package-manager-localized-resources.md)， [SE-0272](https://github.com/apple/swift-evolution/blob/master/proposals/0272-swiftpm-binary-dependencies.md)，[SE-0273](https://github.com/apple/swift-evolution/blob/master/proposals/0273-swiftpm-conditional-target-dependencies.md)等。

---

### Swift 5.2

#### Key Path表达式用作函数

[SE-0249提案](https://github.com/apple/swift-evolution/blob/master/proposals/0249-key-path-literal-function-expressions.md)的实现，Key Path表达式作为函数（Key Path Expressions as Functions）。可以将函数`(Root) -> Value` 简写为`\Root.value`。

例如，我们定义User类型

```swift
struct User {
    let name: String
    let age: Int
    let bestFriend: String?
    
    var cnaVote: Bool {
        age >= 18
    }
}
```

创建几个实例，并且放入数组

```swift
let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]
```

Swift 5.2之后，你可以像下面一样使用key path

```swift
let userNames = users.map(\.name)
print(userNames)
```

而在之前稍微啰嗦一点

```swift
let oldUserNames = users.map { $0.name }
```

当然，你也可以如下使用

```swift
let kp: (User) -> String? = \User.bestFriend
let bestFriends = users.compactMap(kp)
print(bestFriends)
```

注意kp需要显示的写明类型，不然默认是KeyPath类型

![Swift 5.2到5.4新特性整理](https://image.hicc.pro/pro/20210306204848Fg7pI8bsnE8zxPXvY38jGbXk8aLnwtDVTWW7mfcMNknmLVCg9.png)

#### 可调用的值

[提案SE-0253](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md)为Swift带来可调用的值(Callable values of user-defined [nominal types](https://en.wikipedia.org/wiki/Nominal_type_system))。具体来说，如果类型实现了名为的`callAsFunction()`的方法，其类型的实例就能直接调用。

```swift
struct Dice {
    var lowerBound: Int
    var upperBound: Int
    
    func callAsFunction() -> Int {
        (lowerBound...upperBound).randomElement()!
    }
}

let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)
```

你可以正常定义`callAsFunction()`方法，支持`throws`和`rethrows`，可以使用`mutating`。

```swift
struct StepCounter {
    var steps = 0
    
    mutating func callAsFunction(count: Int) -> Bool {
        steps += count
        print(steps)
        return steps > 10_100
    }
}

var steps = StepCounter()

let targetReached = steps(count: 10)
```

这么甜的语法糖是为了什么？

- 更清晰的语法
- 能更有好的开发机器学习（提议的原始动机之一）。

#### 下标可声明默认参数

Swift 5.2 之后，当你使用自定义下标时候，你可以给参数声明默认值了。

```swift
struct PoliceForce {
    var officers: [String]

    subscript(index: Int, default default: String = "Unknown") -> String {
        if index >= 0 && index < officers.count {
            return officers[index]
        } else {
            return `default`
        }
    }
}
//Amy
//Unknown
```

现在可以像如下自定义值

```swift
print(force[-1, default: "The Vulture"])
```

当然自定义下标，你也可以不给参数家label

```swift
struct Multiplier {
  subscript(x: Int, y: Int = 1) -> Int {
    x * y
  }
}

let multiplier = Multiplier()

multiplier[2, 3]
multiplier[4]
```

#### Lazy filter 顺序反转

```swift
let people = ["Arya", "Cersei", "Samwell", "Stannis"]
    .lazy
    .filter { $0.hasPrefix("S") }
    .filter { print($0); return true }
_ = people.count
//Samwell
//Stannis
```

Swift 5.2 之前使用`lazy`，会返回所有，这很违反直觉，因此Swift 5.2 修复了这个问题。

#### 支持使用外作用域值作为默认值

```swift
func outer(x: Int) -> (Int, Int) {
    func inner(y: Int = x) -> Int {
        return y
    }
    return (inner(), inner(y: 0))
}
```

Swift 5.2 之后上述可以编译通过。

#### 更好的错误诊断

Swift 5.2之后，改善了，Swift和SwiftUI的错误提示。

---

### Swift 3 到Swift 5.1

- [Swift 3到5.0 新特性](/p/swift/whats-new-in-swift-3-to-5-1)
- [Swift 5.1新特性](/p/swift/whats-new-in-swift-5-1)

---

### 参考

[What’s new in Swift 5.2](https://www.hackingwithswift.com/articles/212/whats-new-in-swift-5-2): Key path expressions as functions, callAsFunction, and more

[What’s New in Swift 5.2](https://www.raywenderlich.com/9224115-what-s-new-in-swift-5-2): Swift 5.2 is now available as part of Xcode 11.4. In this article, you’ll get an overview of the changes you’ll see moving to Swift 5.2.

[What’s new in Swift 5.3?](https://www.hackingwithswift.com/articles/218/whats-new-in-swift-5-3): Multiple trailing closures, massive package manager improvements, and more.

[What’s new in Swift 5.4?](https://www.hackingwithswift.com/articles/228/whats-new-in-swift-5-4): Multiple variadic parameters, improved implicit member syntax, result builders, and more!

---

原文 / Source: <https://hicc.pro/p/swift/whats-new-in-swift-5-2-to-5-4>
