はじめに
.init()
とは、型が確定している場合に、その型のイニシャライザを呼び出すための省略記法です。
// 1〜5は基本的には同じ意味になる
struct FooScreen: View {
@StateObject private var viewModel = FooViewModel() // 1
@StateObject private var viewModel: FooViewModel = .init() // 2
@StateObject private var viewModel: FooViewModel = FooViewModel() // 3
@StateObject private var viewModel = FooViewModel.init() // 4
@StateObject private var viewModel: FooViewModel = FooViewModel.init() // 5
}
本記事では、私が .init()
をどのような基準で使っているか紹介します。
環境
- OS:macOS Monterey 12.5.1
- Xcode:14.1 (14B47b)
- Swift:5.7.1
○○.init()は使わない
大前提として、私は ○○.init()
を使いません。
冗長だからで、 ○○()
または .init()
のどちらかを使います。
つまり上記の4と5は使いません。
struct FooScreen: View {
@StateObject private var viewModel = FooViewModel() // 1
@StateObject private var viewModel: FooViewModel = .init() // 2
@StateObject private var viewModel: FooViewModel = FooViewModel() // 3
// ❌: 冗長なので使わない
@StateObject private var viewModel = FooViewModel.init() // 4
@StateObject private var viewModel: FooViewModel = FooViewModel.init() // 5
}
.init()を使う基準
.init()
を使う基準を紹介します。
基本的に.init()は使わない
私は基本的に .init()
を使いません。
可読性が下がりやすいのと、わざわざ let foo: Foo = .init()
と書くより let foo = Foo()
と書くほうが簡潔だからです。
可読性が下がることについて、型が明記されておらず、推測もしづらい場合、 .init()
を使うと型が完全にコードから読み取れなくなります。
// 🔺: `with` に渡しているインスタンスの型がコードから読み取れない
let foo = Foo(with: .init(text: ""))
// ⭕: `with` に渡しているインスタンスが `Bar` 型だとわかる
let foo = Foo(with: Bar(text: ""))
Bar
のインスタンスがどこで使われているか確認したい場合も、 .init()
だけだと文字列で検索しづらいです。
型名を書くと冗長になる場合のみ.init()を使う
ではどういうときに .init()
を使うかというと、型名を書くと冗長になる場合です。
大きく2パターン考えました。
型を明記する場合
まずは型を明記する場合です。
プロパティの宣言と初期化を同時に行う場合、私は今まで型を明記することはなかったのですが、koherさんとniwさんのツイートを見て明記することも考えました。
まずkoherさんのツイートの意味ですが、上記の1と2で考えてみます。
struct FooScreen: View {
// 🔺: 失敗可能イニシャライザの場合は `FooViewModel?` 型になる
@StateObject private var viewModel = FooViewModel() // 1
// ⭕: 型を明記しているので必ず非オプショナル型になる
@StateObject private var viewModel: FooViewModel = .init() // 2
}
1だと型推論しているので、失敗可能イニシャライザの場合だと FooViewModel?
型になってしまうのですが、コードからは読み取れません。
2だと型を明記しているので、右辺のイニシャライザが失敗しないことがわかります。
ただ失敗可能イニシャライザを使うことは少ないかもしれませんし、そもそも型が非オプショナルであることを明記する必要があるのか、という疑問もあります。
そこでniwさんのツイートの意味を考えます。
// 🔺: publicなプロパティなのに型が明記されていない
public let bar = Bar(text: "")
// ⭕: privateなプロパティなので、型が明記されていなくても問題ないと考える
private let bar = Bar(text: "")
前者はpublicで他のモジュールから呼び出されるので、型が明記されているほうが使いやすいです。
後者はprivateでそのクラス内のみから呼び出されるので、型が明記されていなくてもそこまで問題ないと考えます。
つまり前者は以下のように型を明記すべきです。
// publicなプロパティで型が明記されている
public let bar: Bar = Bar(text: "")
やっと本題ですが、私はこのように Bar
が重複しているのを冗長だと感じるので、以下のように書きます。
// 🔺: barが重複していて冗長
public let bar: Bar = Bar(text: "")
// ⭕: barが重複せずスマート
public let bar: Bar = .init(text: "")
余談ですが、私が型を明記する基準は今のところ internal
以上のプロパティです。
変数には型を明記しません。
func setObject<V: Encodable>(_ value: V, forKey defaultName: String) async throws {
let jsonEncoder = JSONEncoder() // ⭕: メソッド内の変数ではわざわざ型を明記しない
// ...
}
型が暗黙的にわかる場合
次に型が暗黙的にわかる場合です。
型が明記されていなくても、引数ラベルなどから暗黙的にわかるときも私は .init()
を使います。
以下のような構造体があるとします。
struct SaunaSet {
var sauna: Sauna
var coolBath: CoolBath
struct Sauna {
var time: TimeInterval?
}
struct CoolBath {
var time: TimeInterval?
}
}
こちらの構造体を生成する方法を考えます。
// 🔺: 「Sauna」や「CoolBath」が重複している
let saunaSet = SaunaSet(sauna: Sauna(time: .now), coolBath: CoolBath(time: .now))
// ⭕: 引数ラベルから型が推測できるので `.init()` を使っている
let saunaSet = SaunaSet(sauna: .init(time: .now), coolBath: .init(time: .now))
前者は「Sauna」や「CoolBath」を重複して書いていて、スマートでないと感じます。
後者は引数ラベルから型を推測できるので、 .init()
を使っていても可読性が下がりにくいです。
ただ引数ラベルから型を推測するのは不確定なので、どちらを使うかは好みだと思います。
コンパイルがあまりにも遅くなる場合は.init()を使わない
.init()
が最もコンパイルにかかる時間が遅くなるようです。
そのため、もし .init()
を使ってコンパイルがあまりにも遅くなる場合、.init()
を使わないほうがいいと思います。
まとめ
私が .init()
を使う基準をまとめると以下になります。
- 型が明記されている、または推測できる
- コンパイル時間が遅くない →
.init()
を使う - コンパイル時間が遅い →
.init()
を使わない
- コンパイル時間が遅くない →
- 型が明記されておらず、推測もできない →
.init()
を使わない
おわりに
私が .init()
を使う基準を紹介しました。
好みが分かれる内容だと思うので、他に意見があればコメントをくださると嬉しいです
おまけ
おまけです。
社内での議論
社内で .init()
を使う基準について議論したら、いろいろな意見が出たので箇条書きで紹介します。
- どっちでもいい、特にこだわりはない
-
.init()
だと、型名を変えたときにイニシャライザの呼び出しを変えずに済む -
.init()
はドットで補完できる - ドット始まりのほうがSwifty
- そもそも型を知らないほうがきれいかもしれない
- 場合によって使い分けるのはめんどくさい
Twitterでの議論
Twitterでも様々な意見があったので紹介します。
メソッドの戻り値は型が明記されているので、この使い方は私もありだと思います。
こちらの意見は社内でも出たので、ドットで補完できるのは魅力的なようです。