[Swift] struct 生成時にイニシャライザのデフォルト引数と変数の初期値のどちらが優先されるか?
問題
struct 生成時にイニシャライザのデフォルト引数と変数の初期値のどちらが優先されるか?
答え
デフォルト引数の値が優先される
以下、検証結果です
(1) イニシャライザのデフォルト引数なし、初期値なしの場合
イニシャライザのデフォルト引数なし、初期値なしの場合↓
struct Hoge {
let moge: Int
let fuga: Int
}
let hoge: Hoge = .init(moge: 1, fuga: 2)
print(hoge) // Hoge(moge: 1, fuga: 2)
struct は以下のイニシャライザを自動補完してくれます。
struct Hoge {
let moge: Int
let fuga: Int
// 以下のイニシャライザは自動で補完される
init(moge: Int, fuga: Int) {
self.moge = moge
self.fuga = fuga
}
}
ちなみに class はイニシャライザを自動補完してくれません。
(2) 初期値を設定した場合(let編)
初期値を設定した場合(let編)↓
struct Hoge {
let moge: Int = 0
let fuga: Int = 0
}
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 0, fuga: 0)
注意すべきなのが、以下の記述はできません。
let hoge: Hoge = .init(moge: 1, fuga: 2) // ❌ Argument passed to call that takes no arguments
初期値を与えるとイニシャライザを自動補完してくれないのか、、、と思って、以下のように書いてみたところそんな単純なことではありませんでした。
struct Hoge {
let moge: Int = 0
let fuga: Int = 0
init(moge: Int, fuga: Int) {
self.moge = moge // ❌ Immutable value 'self.moge' may only be initialized once
self.fuga = fuga // ❌ Immutable value 'self.fuga' may only be initialized once
}
}
Immutable value 'self.moge' may only be initialized once
なるほど、let
で初期値を宣言すると、イニシャライザを書くことができないみたいです。(知らなかった、、、)
(3) 初期値を設定した場合(var編)
初期値を設定した場合(var編)↓
struct Hoge {
var moge: Int = 0
var fuga: Int = 0
}
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 0, fuga: 0)
ここまでは let
で宣言した時と同様の挙動です。
続いて、イニシャライザに引数を与えてみます。
let hoge: Hoge = .init(moge: 1, fuga: 2)
print(hoge) // Hoge(moge: 1, fuga: 2)
ちゃんと動きました。やはりイニシャライザを自動補完してくれるようです。
もちろん以下のように書いても問題ありません。
struct Hoge {
var moge: Int = 0
var fuga: Int = 0
// 初期値を設定しても let のときは怒られたイニシャライザを書くことができる
init(moge: Int, fuga: Int) {
self.moge = moge
self.fuga = fuga
}
}
ただし、イニシャライザで指定した値で更新されるため、初期値の定義は意味をなしません。
なので、意味のない記述(のはず)なので Xcode が怒ってくれた方がありがたいです。
(4) イニシャライザのデフォルト引数を設定した場合(let編)
イニシャライザのデフォルト引数を設定した場合(let編)↓
struct Hoge {
let moge: Int
let fuga: Int
init(moge: Int = 1, fuga: Int = 2) {
self.moge = moge
self.fuga = fuga
}
}
// 初期値を省略可能
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 1, fuga: 2)
// もちろん、自由な引数をあたえることができます。
let hoge2: Hoge = .init(moge: 3, fuga: 4)
print(hoge2) // Hoge(moge: 3, fuga: 4)
当たり前の挙動ですね。
(5) イニシャライザのデフォルト引数と初期値の両方を設定した場合(let編)
イニシャライザのデフォルト引数と初期値の両方を設定した場合(let編)↓
struct Hoge {
let moge: Int = 0
let fuga: Int = 0
init(moge: Int = 1, fuga: Int = 2) {
self.moge = moge // ❌ Immutable value 'self.moge' may only be initialized once
self.fuga = fuga // ❌ Immutable value 'self.fuga' may only be initialized once
}
}
初期値を設定した場合(let編)
でも書いたようにそもそもイニシャライザを設定できません。
(6) イニシャライザのデフォルト引数を設定した場合(var編)
イニシャライザのデフォルト引数を設定した場合(var編)↓
struct Hoge {
var moge: Int
var fuga: Int
init(moge: Int = 1, fuga: Int = 2) {
self.moge = moge
self.fuga = fuga
}
}
// 初期値を省略可能
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 1, fuga: 2)
// もちろん、自由な引数をあたえることができます。
let hoge2: Hoge = .init(moge: 3, fuga: 4)
print(hoge2) // Hoge(moge: 3, fuga: 4)
当たり前の挙動ですね。
(7) 【本題】イニシャライザのデフォルト引数と初期値の両方を設定した場合(var編)
イニシャライザのデフォルト引数と初期値の両方を設定した場合(var編)↓
struct Hoge {
var moge: Int = 0
var fuga: Int = 0
init(moge: Int = 1, fuga: Int = 2) {
self.moge = moge
self.fuga = fuga
}
}
let hoge: Hoge = .init()
print(hoge) // Hoge(moge: 1, fuga: 2)
このようにイニシャライザのデフォルト値が優先されるみたいですね。
もちろんイニシャライザの引数で指定すればそれが上書きされます。
let hoge: Hoge = .init(moge: 3, fuga: 4)
print(hoge) // Hoge(moge: 3, fuga: 4)
となると、イニシャライザのデフォルト引数を設定した場合は、変数の初期値の記述は意味がないみたいですね。
そうなら、Xcode 側でその記述が意味ないことを教えてほしいですね。(本当に意味がないかはわかりませんが、、、)
補足
『イニシャライザのデフォルト値が優先されるということは、実は変数の初期値が先に代入されてイニシャライザの値で再代入されているのでは?』
と思ったので、以下のように didSet
用意して、実験してみました。
struct Hoge {
var moge: Int = 0 {
didSet {
print("oldValue: \(oldValue)")
print("moge: \(moge)")
}
}
var fuga: Int = 0 {
didSet {
print("oldValue: \(oldValue)")
print("fuga: \(fuga)")
}
}
init(moge: Int = 1, fuga: Int = 2) {
self.moge = moge
self.fuga = fuga
}
}
var hoge: Hoge = .init() // 出力なし
しかしながら、上記のように didSet
は走らなかったので、そういうわけではなさそうです。
以上になります。
Discussion