データクラスを簡単に生成する方法(Swift)

## はじめに

以下のような構造体があるとします。

```swift:SaunaSet.swift
struct SaunaSet {
  var sauna: Sauna
  var coolBath: CoolBath
  var relaxation: Relaxation

  struct Sauna {
    var time: TimeInterval?
  }

  struct CoolBath {
    var time: TimeInterval?
  }

  struct Relaxation {
    var time: TimeInterval?
    var place: String?
    var way: String?
  }
}
```

本記事では、データ構造のみを持つ構造体を「データクラス」と呼ぶことにします。

このようなデータクラスで、「値がすべて `nil` 」や「予めデフォルト値がセットされている」、「プレビューやテスト用にいろいろな値がセットされている」など、様々なパターンが欲しいときがあります。

そのようなときにどう実装するのがいいか紹介します。

## 環境

- OS：macOS Monterey 12.5.1
- Xcode：14.1 (14B47b)
- Swift：5.7.1

## 様々なデータクラスを生成する

様々なデータクラスを生成する方法を紹介します。

### 値がすべてnil

先ほどの例で、値がすべて `nil` のインスタンスが欲しいとします。

普通に生成すると以下のようになります。

```swift
let saunaSet = SaunaSet(sauna: .init(time: nil), coolBath: .init(time: nil), relaxation: .init(time: nil, place: nil, way: nil))
```

1箇所のみで生成するならいいですが、複数箇所で生成すると、毎回この量を書くのは大変です。

そこで `CGRect` の `.null` に倣い、各データクラスに `.null` を用意します。

https://developer.apple.com/documentation/coregraphics/cgrectnull

```diff_swift:SaunaSet.swift
struct SaunaSet {
+ static var null: Self { .init(sauna: .null, coolBath: .null, relaxation: .null) }
+ 
  var sauna: Sauna
  var coolBath: CoolBath
  var relaxation: Relaxation

  struct Sauna {
+   static var null: Self { .init(time: nil) }
+ 
    var time: TimeInterval?
  }

  struct CoolBath {
+   static var null: Self { .init(time: nil) }
+ 
    var time: TimeInterval?
  }

  struct Relaxation {
+   static var null: Self { .init(time: nil, place: nil, way: nil) }
+ 
    var time: TimeInterval?
    var place: String?
    var way: String?
  }
}
```

これで `SaunaSet` 自体も `.null` だけで、要素がすべて `nil` のインスタンスを生成できるようになります。

```diff_swift
- let saunaSet = SaunaSet(sauna: .init(time: nil), coolBath: .init(time: nil), relaxation: .init(time: nil, place: nil, way: nil))
+ let saunaSet: SaunaSet = .null
```

`CGRect` には要素がすべて `0` の `.zero` も用意されているので、こちらも参考になります。

https://developer.apple.com/documentation/coregraphics/cgrect/1455437-zero

`.empty` や `.blank` のようなプロパティを用意するのもいいと思います。

### プレビュー用の値がセットされている

先ほどの例で、プレビュー用に様々な値がセットされたインスタンスが欲しいとします。

基本的には `.null` と同様ですが、プレビューはデバッグ時にしか使わないため、エクステンションで一番下に区切って `#if DEBUG` で括るのがオススメです。

```diff_swift:SaunaSet.swift
struct SaunaSet {
  // ...
}
+ 
+ #if DEBUG
+ extension SaunaSet {
+   static var preview: Self {
+     .init(
+       sauna: .init(time: 5 * 60),
+       coolBath: .init(time: 30),
+       relaxation: .init(
+         time: 10 * 60,
+         place: "外気浴",
+         way: "インフィニティチェア"
+       )
+     )
+   }
+ }
+ #endif
```

これでデバッグ時のみ `.preview` でプレビュー用のインスタンスを取得できます。

```swift:SakatsuView.swift
struct SakatsuView: View {
  var saunaSet: SaunaSet
  // ...
}

struct SakatsuView_Previews: PreviewProvider {
  static var previews: some View {
    SakatsuView(saunaSet: .preview) // プレビュー用のインスタンスを取得している
  }
}
```

### デフォルト値がセットされている

先ほどの例で、デフォルト値がセットされているインスタンスが欲しいとします。

`.null` に倣って `.default` を実装すると以下のようになります。
デフォルト値は予約語なので、バッククォートで括る必要があります。

```diff_swift:SaunaSet.swift
struct SaunaSet {
+ static var `default`: Self { .init(sauna: .default, coolBath: .default, relaxation: .default) }
+ 
  var sauna: Sauna
  var coolBath: CoolBath
  var relaxation: Relaxation

  struct Sauna {
+   static var `default`: Self { .init(time: nil) }
+ 
    var time: TimeInterval?
  }

  struct CoolBath {
+   static var `default`: Self { .init(time: nil) }
+ 
    var time: TimeInterval?
  }

  struct Relaxation {
+   static var `default`: Self { .init(time: nil, place: nil, way: nil) }
+ 
    var time: TimeInterval?
    var place: String?
    var way: String?
  }
}
```

ただデフォルト値の場合は他と異なり、プロパティやイニシャライザの引数にもデフォルト値をセットできます。

```diff_swift:SaunaSet.swift
struct Sauna {
- var time: TimeInterval?
+ var time: TimeInterval? = nil // プロパティにデフォルト値をセットできる
}
```

```diff_swift:SaunaSet.swift
struct Sauna {
  var time: TimeInterval?
+ 
+ init(time: TimeInterval? = nil) { // イニシャライザの引数にデフォルト値をセットできる
+   self.time = time
+ }
```

`.default` を用意するか、Swiftの機能を使うか、どちらがいいのでしょうか。

答えはケースバイケースだと思います。

「デフォルト値のインスタンスに名前を付けたい」「明示的にデフォルト値のインスタンスを生成したい」なら `.default` を用意し、「一部のプロパティのみデフォルト値を使いたい」「プロパティごとにデフォルト値を使うか判断したい」ならSwiftの機能を使う、がよさそうです。

https://twitter.com/jrsaruo_tech/status/1590279864523034625

「 `.default` は共有するインスタンスを返す慣習があるので違和感がある」という意見もあり、まさにその通りだと思いました。
私はどちらもデフォルト値には変わらないので許容していますが、避けたほうがいいかもしれません。

https://twitter.com/griffin_stewie/status/1590626552693153794

## まとめ

一言でまとめると「データクラス側に生成ロジックを持つと使い回せて便利」です。

## おわりに

これでデータクラスを簡単に生成できます。
他にみなさんがやっている方法があれば、コメントなどで教えていただけると嬉しいです :relaxed: