⚡️令和 5 年最新版⚡️ Swift コードの書き方をアップデートする


:::note info
この記事は [セーフィー株式会社 Advent Calendar 2023](https://adventar.org/calendars/8881) 7 日目の記事です。
:::

## はじめに

今年は Swift 5.9 が発表されました。

[Macros](https://developer.apple.com/documentation/swift/macros)
実装も AST も使いどころも難しい😣

[Observation](https://developer.apple.com/documentation/observation)
`iOS 17.0+` だとプロダクション投入はちょっと。。😓

そんな我々のために、 Swift 5.9 を含むここ 1 〜 2 年ほどの Swift アップデートで可能となった、明日からでも使える Swift コードの書き方をいくつか紹介します。

- 個人的によく使う / よく使われるであろうもの
- Swift 5.9 (Xcode 15.0) 以降なら Deployment Target を気にせず利用できる

といったものを中心にまとめました。

## Optional Binding
### `if let hoge = hoge`

`if let` の shorthand 記法が導入されたため、同名の変数名で unwrap されたものを簡単に用意できるようになりました。


```swift:🙁 これまで
let hoge: Hoge? = ...
if let hoge = hoge {
  // unwrap された `hoge` を利用可能
}
```

```swift:🙂 これから
let hoge: Hoge? = ...
if let hoge {
  // unwrap された `hoge` を利用可能
}

// 複数個も対応
let fuga: Fuga? = ...
if let hoge, let fuga {
  // unwrap された `hoge`, `fuga` を利用可能
}
```

また後述するのですが、 `guard let` や、 `while let` といった各種 Optional Binding でも同様の記述ができるようになっています。

対応バージョン、関連 Proposal は以下の通りです。

- Swift 5.7 (Xcode 14.0) 〜
- [SE-0345 `if let` shorthand for shadowing an existing optional variable](https://github.com/apple/swift-evolution/blob/main/proposals/0345-if-let-shorthand.md)

### `guard let self = self else { return } ...`

つい手癖で書いてしまう `guard let self = self else { return } ...` の処理ですが、段階を踏んでかなり簡潔に記述できるようになりました。

```swift:🙁 これまで
hoge.callback = { [weak self] in
  guard let `self` = self else { return }

  self.fuga()
}
```

```swift:🙂 これから
hoge.callback = { [weak self] in
  guard let self else { return }  // shorthand 記法

  fuga()  // `self.` を省略可能
}
```

だいぶ以前は [`guard let self = self` ができてしまうのはバグ](https://qiita.com/peka2/items/c4c05b7c78a8b8ea4910)といったような話もあったのですが、複数の Proposal を通して、上述のように Swift らしいシュッっとしたコードに収まるようになりました。

関連する Proposal としては、

1. `guard let self = self`　が正式に OK になる
    - Swift 4.2 (Xcode 10.0) 〜
    - [SE-0079 Allow using optional binding to upgrade `self` from a weak to strong reference](https://github.com/apple/swift-evolution/blob/main/proposals/0079-upgrade-self-from-weak-to-strong.md)
1. `guard let self` の shorthand 記法導入 (前セクションの通り)
    - Swift 5.7 (Xcode 14.0) 〜
    - [SE-0345 `if let` shorthand for shadowing an existing optional variable](https://github.com/apple/swift-evolution/blob/main/proposals/0345-if-let-shorthand.md)
1. `self.` が省略可能となる
    - Swift 5.8 (Xcode 14.3) 〜
    - [SE-0365 Allow implicit `self` for `weak self` captures, after `self` is unwrapped](https://github.com/apple/swift-evolution/blob/main/proposals/0365-implicit-self-weak-capture.md)

で、 Swift 5.8 で一応の完成を見ています。

## if 式 / switch 式

制限はありますが、 if / switch が式 (expression) としても扱えるようになりました。

具体的に何が嬉しいかというと、以下のような書き方ができるようになります。

```swift:🙁 これまで
func hoge(type: MetaType) {
  let evaluation: String
  
  switch type {
    case .hoge:
      evaluation = "good!"
    case .fuga:
      evaluation = "not bad."
    case .piyo:
      evaluation = "very bad..."
  }
  ...
}
```

```swift:🙂 これから
func hoge(type: MetaType) {
  let evaluation: String = switch type {
    case .hoge:
      "good!"
    case .fuga:
      "not bad."
    case .piyo:
      "very bad..."
  }
  ...
}
```

これまでもコンパイラレベルで変数初期化時の網羅性チェックにてエラーとしてくれていたのですが、より直感的な表現での記述が可能となりました。

また、 Proposal での動機の一つとしても挙げられているのですが、 Kotlin / Android Studio でよく suggest される `return` の lift out についても可能となりました。

```swift:🙁 これまで
func hoge(type: MetaType) -> String {
  if type == .hoge {
    return "hoge" 
  } else if type == .fuga {
    return "fuga"
  } else {
    return "unknown"
  }
}
```

```swift:🙂 これから
func hoge(type: MetaType) -> String {
  return if type == .hoge {
    "hoge" 
  } else if type == .fuga {
    "fuga"
  } else {
    "unknown"
  }
}
```

注意点として、

> Each branch of the if, or each case of the switch, must be a single expression.

であることが求められます。

このため、複数行に渡る複雑な処理やログ出力を仕込むために 1 行追加するといった場合への適用が難しく、他言語での if / switch 式の感覚とは異なる部分があります。

対応バージョン、関連 Proposal は以下の通りです。

- Swift 5.9 (Xcode 15.0) 〜
- [SE-0380 `if` and `switch` expressions](https://github.com/apple/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md)

## `extension Array where Element = ... { }` 

配列で表現されるモデルに対して、 `extension Array` や `extension Sequence` 等で便利メソッドを生やすといったことがあるかと思います。

この際、 `where Element == ...` でやや冗長な記述が発生するのですが、これについても直感的な記述が可能となりました。

```swift:🙁 これまで
extension Array where Element == Hoge {
  func fuga() -> String {
    ...
  }
}
```

```swift:🙂 これから
extension Array<Hoge> {
  func fuga() -> String {
    ...
  }
}
```


対応バージョン、関連 Proposal は以下の通りです。

- Swift 5.7 (Xcode 14.0) 〜
- [SE-0346 Lightweight same-type requirements for primary associated types](https://github.com/apple/swift-evolution/blob/main/proposals/0346-light-weight-same-type-syntax.md)
- [SE-0361 Extensions on bound generic types](https://github.com/apple/swift-evolution/blob/main/proposals/0361-bound-generic-extensions.md)

## `ViewBuilder` に並べられる View の数

SwiftUI に入門すると必ずぶつかる `View` が 10 個まで並べられない問題が解消されました。

```swift:🙁 これまで
var body: some View {
  VStack {
    Text("01")
    Text("02")
    Text("02")
    Text("04")
    Text("05")
    Text("06")
    Text("07")
    Text("08")
    Text("09")
    Group {  // `Group` でラップするワークアラウンド
      Text("10")
      Text("11")
    }
  }
}
```

```swift:🙂 これから
var body: some View {
  VStack {
    Text("01")
    Text("02")
    Text("02")
    Text("04")
    Text("05")
    Text("06")
    Text("07")
    Text("08")
    Text("09")
    Text("10")
    Text("11")
  }
}
```

以前は、

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178830/9a0658f9-7f36-85c9-da5e-5dfe016b8d05.png">


で宣言されていた `ViewBuilder.buildBlock` の処理が

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178830/150694f6-73de-5cc2-d867-d33adf04b100.png">

[buildBlock(_:)](https://developer.apple.com/documentation/swiftui/viewbuilder/buildblock(_:)-2bj1g)

でまとまってくれたためで、 Swift 5.9 で導入された _Value and Type Parameter Packs_ が寄与しています。

よくある `iOS 17.0+` な 新 API への置き換えではないため、 Deployment Target を気にせず利用できます。

対応バージョン、関連 Proposal は以下の通りです。

- Swift 5.9 (Xcode 15.0) 〜
- [SE-0393 Value and Type Parameter Packs
](https://github.com/apple/swift-evolution/blob/main/proposals/0393-parameter-packs.md)
- [SE-0398 Allow Generic Types to Abstract Over Packs](https://github.com/apple/swift-evolution/blob/main/proposals/0398-variadic-types.md)


## 参考記事

https://qiita.com/frosted_bird/items/83c1caf993030773c6bc

https://hackmd.io/@nanashiki/Bys3h2Wrn

https://www.hackingwithswift.com/articles/258/whats-new-in-swift-5-9