そもそもcustom/memberwise initializerって?
swift.org参考
Memberwise Initializers for Structure Types
Structure types automatically receive a memberwise initializer if they don’t define any of their own custom initializers.
構造体タイプは、独自のカスタムイニシャライザを定義しない場合、自動的にメンバーワイズイニシャライザを受け取ります。
つまり以下の解釈で問題なさそうです。
- custom initializer: 手動で書くinit
- memberwise initializer: structにcustom initializerがない場合に自動生成されるinit
本題
環境
- Xcode:14.1 (14B47b)
- Swift:5.7.1
- Swift Playground
問題
いきなりですが問題です。
第1問
以下のコードの出力はどうなるでしょうか?
(CountA
のinitは何回実行されるでしょうか?)
final class CountA {
static var count = 0
public init() {
Self.count += 1
}
}
struct A {
var count: CountA = CountA()
// custom init
init(count: CountA) {
self.count = count
}
}
let a = A(count: CountA())
print(CountA.count) // ?
答えは 2 です。
デフォルト値の初期化はinitとは別に行われるため、var count: CountA = CountA()
とlet a = A(count: CountA())
で2回の処理が行われ、 CountA
のinitは2回実行されます。
第2問
以下のコードの出力はどうなるでしょうか?
(CountB
のinitは何回実行されるでしょうか?)
final class CountB {
static var count = 0
public init() {
Self.count += 1
}
}
struct B {
var count: CountB = CountB()
// memberwise init
}
let b = B(count: CountB())
print(CountB.count) // ?
答えは 1 です。
なぜこうなるの?
調べた限りだとSwift.orgにこの仕様に関しては詳しく書いてありませんでした。
コンパイル後の出力を見れば何かわかる気がしますが、そこまでの知識がないのでわかりません。有識者の方、コメントよろしくお願いします。
なぜこうなるの・考察
custom init版でCountAが2回initされることに関しては、そこまで不自然ではない(理解できる挙動)ですね。
struct A {
var count: CountA = CountA() // ここでもCountA生成
// custom init
init(count: CountA) {
self.count = count
}
}
let a = A(count: CountA()) // ここでもCountA生成
「デフォルト値の初期化・代入はinitとは別に行われる」 ということを意味しています。
ところが、memberwise initだと1回しか初期化が起こりません。つまり以下のようになっています。
struct B {
var count: CountB = CountB() // <- ここでCountB生成は起きない
// memberwise init
}
let b = B(count: CountB()) // <- ここでCountB生成は起こる(当たり前)
これは、「memberwise initはデフォルト値の生成プロセスに影響・関与する」ということになります。
memberwise initは単なる「自動で生成してくれるinit」ではなく 、「特殊なinit」という認識の方が正しそうです。
C++やJavaでは、「イニシャライザ=変数を初期化・定義するフェーズ」「コンストラクタ=クラスのインスタンスを作成するフェーズ」が分かれていたりします。その概念をSwiftにも当てはめると、
- memberwise initはイニシャライザ(寄り)
- custom initはコンストラクタ(寄り)
という感じになるのかもしれません。
それで...?
はい、ただの豆知識です。
紹介しといて何ですが、memberwise initによるデフォルト値生成削減の挙動はコスト的には良い物ですが、直感的ではないため、 狙って利用するべきではない と思います。structをpublicにしたら強制的にmemberwise init使えなくなリますし...
custom initで2回初期化が走る挙動をご存知の方はもうやられているかと思いますが、「デフォルト値を設定しているが、initでの代入を受け付けたい」というケースは、デフォルト引数(もしくはinitのオーバーロード)で吸収できるので、これが一番丸いと思います。
struct A {
var count: CountA
// custom init
init(count: CountA = CountA()) {
self.count = count
}
}
let a = A(count: CountA())
余談
Xcodeの補完で"generate memberwise initializer"ってありますが、上記問題を考えても、swift.orgの定義を考えても、あれは"memberwise initializerと同じ定義を持つcustom initializer"であって、"memberwise initializer"じゃない気がしています、変えた方がいいような...