はじめに
ごきげんようー( ∴ )
いつも慣れ親しんでいるSwiftなのですが、変数定義に {} の構文を利用するいくつかのパターンがあってそれぞれが異なる意味を表しています。にも関わらず {} を利用するというイメージでこれらの意味をまとめてしまっていると混乱してしまうことがあるかもしれません。私のようにね
事象
変数定義に{}を使うパターンは3つあるのですが、それらを列挙したときにそれぞれの挙動の違いが想像できるかどうかテストをしてみましょう。
// 一見すると全部同じようなことをしているように見えますが...
// foo, bar, bazの挙動の違いがわかりますでしょうか?
class Gestaltzerfall {
var foo: Node {
return Node(name: "foo")
}
let bar = {
return Node(name: "bar")
}()
let baz = {
return Node(name: "baz")
}
}
なるべく多くの人をハメたいのでバイアスのかかったコードにしてあります。初耳の人や怪しい人のためにここで一緒に確認しておきましょう。
変数定義で {} 構文を使う3パターン
変数foo
, bar
, baz
はそれぞれ以下の挙動を定義していましたが、どうでしょうか。わかりましたでしょうか。
-
foo
- Computed propertiesを定義 -
bar
- 即時関数(クロージャー)の戻り値を代入 -
baz
- クロージャーを代入
それぞれの挙動の違いを確認していきましょう。
Computed propertiesを定義
// Computed properties
var foo: Node {
get {
return Node(name: "foo")
}
}
Computed properties自体はget/setといった読み書きの制御に特化した特徴を持ったプロパティになります。テストの例ではgetの記述を省略して読み取り専用としている意地悪な記述でした。getと明示すると明らかにComputed propertiesと理解してもらえるかと思います。
変数にアクセスする度にgetの処理が実行されます。
即時関数(クロージャー)の戻り値を代入
// 即時関数の返り値を代入
let bar: Node = {
return Node(name: "bar")
}()
保持しないクロージャーの結果を変数へ代入しています。早い話が関数の戻り値を変数に入れているだけです。
初期化時にクロージャー内部の処理が一度実行されます。
クロージャーを代入
// クロージャーを代入
let baz: () -> Node = {
() -> Node in
return Node(name: "baz")
}
保持したいクロージャーを変数へ格納しています。
呼び出しの度にクロージャーが実行されます。
それぞれ呼び出してみる
// 初期化時にnameをprintするなにか
struct Node {
init(name: String) {
self.name = name
print(name)
}
let name: String
}
Nodeはfoo
, bar
, baz
が戻り値にしている構造体です。
デバッグ用に初期化時にログを出力するようにしました。
let g = Gestaltzerfall()
// 呼ばれるたびにNodeを初期化
g.foo
g.foo
// クラスをインスタンス化したときにNodeが初期化
g.bar
g.bar
// 呼ばれるたびにNodeを初期化
g.baz()
g.baz()
戻り値を無視しているのでプログラム的にはナンセンスな記述ですが、初期化時にログを出力するようにしたのでデバッグエリアで挙動を確認できます。
bar
foo
foo
baz
baz
戻り値の結果だけ見るとfoo
とbaz
の挙動は同じですね。
bar
はクラスのインスタンス化時に一度だけ代入されています。
おわりに
体系的に学習された方にとっては謎な事象に思われるかと思いますが、見よう見まねでやってるとしばしばこんなことではまったりします
ちゃらんぽらんなので認識間違いあったら教えいただけるとありがたいです。