はじめに
以下のような構造体があるとします。
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
のインスタンスが欲しいとします。
普通に生成すると以下のようになります。
let saunaSet = SaunaSet(sauna: .init(time: nil), coolBath: .init(time: nil), relaxation: .init(time: nil, place: nil, way: nil))
1箇所のみで生成するならいいですが、複数箇所で生成すると、毎回この量を書くのは大変です。
そこで CGRect
の .null
に倣い、各データクラスに .null
を用意します。
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
のインスタンスを生成できるようになります。
- 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
も用意されているので、こちらも参考になります。
.empty
や .blank
のようなプロパティを用意するのもいいと思います。
プレビュー用の値がセットされている
先ほどの例で、プレビュー用に様々な値がセットされたインスタンスが欲しいとします。
基本的には .null
と同様ですが、プレビューはデバッグ時にしか使わないため、エクステンションで一番下に区切って #if DEBUG
で括るのがオススメです。
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
でプレビュー用のインスタンスを取得できます。
struct SakatsuView: View {
var saunaSet: SaunaSet
// ...
}
struct SakatsuView_Previews: PreviewProvider {
static var previews: some View {
SakatsuView(saunaSet: .preview) // プレビュー用のインスタンスを取得している
}
}
デフォルト値がセットされている
先ほどの例で、デフォルト値がセットされているインスタンスが欲しいとします。
.null
に倣って .default
を実装すると以下のようになります。
デフォルト値は予約語なので、バッククォートで括る必要があります。
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?
}
}
ただデフォルト値の場合は他と異なり、プロパティやイニシャライザの引数にもデフォルト値をセットできます。
struct Sauna {
- var time: TimeInterval?
+ var time: TimeInterval? = nil // プロパティにデフォルト値をセットできる
}
struct Sauna {
var time: TimeInterval?
+
+ init(time: TimeInterval? = nil) { // イニシャライザの引数にデフォルト値をセットできる
+ self.time = time
+ }
.default
を用意するか、Swiftの機能を使うか、どちらがいいのでしょうか。
答えはケースバイケースだと思います。
「デフォルト値のインスタンスに名前を付けたい」「明示的にデフォルト値のインスタンスを生成したい」なら .default
を用意し、「一部のプロパティのみデフォルト値を使いたい」「プロパティごとにデフォルト値を使うか判断したい」ならSwiftの機能を使う、がよさそうです。
「 .default
は共有するインスタンスを返す慣習があるので違和感がある」という意見もあり、まさにその通りだと思いました。
私はどちらもデフォルト値には変わらないので許容していますが、避けたほうがいいかもしれません。
まとめ
一言でまとめると「データクラス側に生成ロジックを持つと使い回せて便利」です。
おわりに
これでデータクラスを簡単に生成できます。
他にみなさんがやっている方法があれば、コメントなどで教えていただけると嬉しいです