19新卒の永田です。10月の配属以降、iOSアプリの開発に取り組んでいます。 今回はタイトルの通り、Initialization Closureについてまとめようと思います。
TL; DR
- Initialization Closureは Stored Property の初期化に使われる書き方。
- Computed Propertyではないので、
{}の中は 1度しか呼ばれない。
未知との遭遇
現在開発中のアプリでUIViewControllerのライフサイクルとAuto Layoutの反映タイミングに悩まされ、
viewDidLayoutSubview()で最初の一回だけ処理をしたくなり、この記事にたどり着きました。
実際に以下のようなコードを書けば問題は解決したのですが、何がどうなっているのか・なぜ上手くいってるのかがわかりませんでした。
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = initCollectionViewFlowLayout
}
private lazy var initCollectionViewFlowLayout : Void = {
// ここで色々設定する
// frame、boundsとか使う
}()
何がわからんのか
当時わからなかったことをまとめるとこんな感じだったと思います。
- 見た目Computed Propertyと似てるけど何が違うのか
- 何故これで
initCollectionViewFlowLayoutの中身が一度しか呼ばれないのか {}の後の()は何者なのか
最近ようやくこの謎が解けた(っぽい)ので、これらの点について話をします。
Computed Propertyとの違い
変数名の後に
{ return }みたいなのがあるのでComputed Propertyと混同してしまったのですが、
この
initCollectionViewFlowLayoutはInitialization Closureという書き方で
Stored Propertyとして定義されているみたいです(
=があるのでそれもそうか…という感じ)。
Initialization Closureとは
Stored Propertyの初期化の際に、初期値としてクロージャの実行結果を渡す書き方のことです。
例1 ) 要素数50のフィボナッチ数列 (Stored Property)
let fibonacci: [Int] = {
var temporaryArray = [1, 1]
let numberOfElement = 50
for _ in 0..<numberOfElement - temporaryArray.count {
let nextElement = temporaryArray[temporaryArray.count - 2] + temporaryArray[temporaryArray.count - 1]
temporaryArray.append(nextElement)
}
return temporaryArray
}()
上の例では、フィボナッチ数列の初期化を行うクロージャの返り値を
定数
fibonacciに渡しています(return temporaryArray)。
{}の中で初期化の処理をクロージャとして定義して、最後に()をつけることで
そのクロージャを即実行して
[Int]の値を取得しているという感じです。
最後の
()を付けないと、fibonacciにクロージャ(この場合() -> [Int]のクロージャ)を代入する形になってしまい、コンパイルエラーが起きます。
何故処理が一度しか呼ばれないのか
答えはシンプルで、
initCollectionViewFlowLayoutが Stored Propertyだから です。
Computed Propertyは値を保持しないので参照するたびに
{}の中の計算が走ります(例2)が、
Stored Propertyの場合は値を保持するので、参照しても
{}内の計算は行われず、初期化の時に{}内でreturnされた値を使うだけになります(例3)。
例2 ) 要素数50のフィボナッチ数列 (Computed Property)
var fibonacci: [Int] {
var temporaryArray = [1, 1]
let numberOfElement = 50
for _ in 0..<numberOfElement - temporaryArray.count {
let nextElement = temporaryArray[temporaryArray.count - 2] + temporaryArray[temporaryArray.count - 1]
temporaryArray.append(nextElement)
}
print("おるで")
return temporaryArray
}
for i in 0..<fibonacci.count {
print(fibonacci[i])
}
おるで // fibonacci.countでの参照で呼ばれている
おるで // 以降、print(fibonacci[i])での参照で呼ばれている
1
おるで
1
おるで
2
(中略)
おるで
4807526976
おるで
7778742049
おるで
12586269025
例3 ) Initialization Closureが1度しか呼ばれていないことの確認
let fibonacci: [Int] = {
var temporaryArray = [1, 1]
let numberOfElement = 50
for _ in 0..<numberOfElement - temporaryArray.count {
let nextElement = temporaryArray[temporaryArray.count - 2] + temporaryArray[temporaryArray.count - 1]
temporaryArray.append(nextElement)
}
print("おるで")
return temporaryArray
}()
for i in 0..<fibonacci.count {
print(fibonacci[i])
}
おるで // 初期化のタイミングで呼ばれている
1
1
2
(中略)
4807526976
7778742049
12586269025
まとめ
ここまでの内容をまとめると、今回の
initCollectionViewFlowLayoutは、Initialization Closureの正規の使い方というよりかは、その仕組みを応用した手法なのかな?と感じました。これ思いついた人賢いな〜〜とシンプルに感心。
ここまで理解するのにかなり時間がかかってしまいましたが、また便利そうな手法を1つ知ることができたのでこれから上手く使っていこうと思います。
参考
永田駿平
iOSアプリを作っています
音楽とガジェットが好きです