こんにちは、株式会社ZOZOでiOSエンジニアをしております@shogunzozoです!!
自身2本目の記事はライフサイクル関係についての記事を書いてみました。
普段何気無く使っているライフサイクルに関するメソッド群も使い方次第でパフォーマンスに直結してくると痛感する日々です。
プロダクトを直接的に実装するエンジニアとしてユーザーのUX向上は命題です。そのため、今一度根幹であるライフサイクルに関して見直しせっかくなのでQiitaに残す事にしました。
本記事の内容
初学者にもわかりやすく、iOSアプリのライフサイクルに関してまとめてあります。
困った時の辞書代わりに使って頂ければ幸いです。
そもそもライフサイクルって何??
簡略化すると、一般にアプリが立ち上がり画面が描画され消える(次の画面に進んだり、アプリを閉じたり)までの一連の流れを言います。
つまり、アプリで画面のライフサイクルというと以下2点が大きな意味合いになると思います。
- 画面データがいつ読み込まれ、レイアウトはどのタイミングで計算されているのか
- その瞬間瞬間はどういったメソッドで実行されているのか
特に、ライフサイクルを実現させている各メソッドの理解がアプリの読み込みやパフォーマンスを考えうる上でも重要だと思います。
以後、アプリ上に画面が表示されるにされる際直接関わってくるライフサイクルイベントを順番に説明していきたいと思います。
以下はライフサイクルのイメージです。
画面が読み込まれる際に起きるライフサイクルイベント
画面が表示される時は見えているのは何らかのUIViewControllerです。それは、新たにインスタンスが生成されているとも言い換える事ができます。
以下のイベントは画面が初回表示された時のみ呼ばれるイベントです。
loadView()
初回表示するUIViewControllerのデータを読み込みます。ただし、この時点ではView Controllerがインスタンス化されていないためViewが生成されていません。
そして、nib・xibファイルを使用しない場合カスタムViewの初期化を行う場所の様です。
StoryboardやxibなどInterfaceBuilderを利用している際はこのメソッドを呼び出してはいけない様です。
viewDidLoad()
viewControllerのViewがロードされた後に呼ばれるメソッドです。
通常ここで行う事としては、UI上にあるパーツ(ボタンやラベルなど)の初期化を行います。
ここで注意する事として、この時点では画面は表示されていないという事です。また、制約などレイアウトの計算も完了していません。
そのため、制約など配置に関する実装をここで行うことは推奨されていません。
画面表示時ViewControllerライフサイクルイベント
viewWillAppear()
viewControllerが画面に表示される直前に呼ばれ、該当viewControllerが表示されるたびに呼ばれるメソッドです。
まだviewが表示されていないので計算コストの高い処理は避けましょう。
updateViewConstraints()
ここでまず覚えておきたい事は、制約は子ビューから更新されるという事です。
また、こちらはself.viewに更新が必要な時にコールされて制約が更新されます。
viewWillLayoutSubViews()
これはviewのレイアウトを開始する直前に呼ばれます。同時に、viewのlayoutSubViews()が実行されます。
このlayoutSubViews()に関しては後ほど解説します。
viewDidLayoutSubViews()
viewのレイアウトが完了した時点で呼ばれるメソッドになります。
この時点で、self.view.frameが決定します。そのため、frameベースで制約をコーディングで入れる際はここで入れましょう。
frameが定まっていないと、そもそも制約をかける事が出来ずに意図した結果が得られないと思います。
viewDidAppear()
viewが表示された直後に呼ばれます。ここではviewのデータレンダリングが完了しています。
そのため、ここでUIの更新系の処理でなく通信などの処理を実行するのに適しています。(GAなどデータを取る系処理)
覚えておきたいUIView更新系メソッド
updateConstraints()
自身で作成したカスタムビューかつオーバーライド可能です。これは上記でも述べましたが、サブビューの制約から更新していきます。
ここで重要な点は、overrideした際メソッド内最後にsuper.updateConstraints()を必ず呼ぶ必要があります。
これは上記でも述べましたが、制約の更新は子ビュー(サブクラス)から進むためです。
ただ、こちらをオーバーライドしてまでパフォーマンスを高める必要のある画面作りは推奨されていません。
setNeedsUpdateConstraints()
事前に更新の予約をセットすることにより、無駄な制約計算の抑制を行います。こちらは純粋にフラグを立てるだけですので非常に軽量です。
updateConstraintsIfNeeded()
システムがレイアウトを更新したい時に呼ばれます。最新の制約を得たい時にこちらのメソッドを使う事も可能です。
上記、setNeedsUpdateConstraints()がコールされた後に呼ばれます。
layoutSubviews()
こちらもオーバーライド可能です。どういったケースでオーバーライドするかというと、layoutSubviewsがコールされる時点ではすべての制約がframe値への置き換え計算が終わっています。そのため、最終的なframeのアウトプットに何か変更を入れたい時に使われます。
また、オーバーライドした場合メソッドない最初の行で super.layoutSubviews()をコールする必要があります。
それにより、制約からサブビューのframeが決定され、操作できるようになります。
ちなみに、layoutSubviews()自体は単体で呼んではいけません。
setNeedsLayout()
このメソッドは、self.viewをルートとしたサブビュー全てに「再描画が必要」というフラグの設定を行う。
setNeedsLayout()ではサブビューも含めて自身を起点とした配下のサブビューすべてを再描画するので、軽量なメソッドとなっている。
layoutIfNeeded()
このメソッドをコールしたself.view&サブビューに更新の必要があった際に即更新処理が走ります。小さな変更でも該当ビューが全て読まれるので比較的思い処理と言われます。
また、こちらのメソッドを使う際に注意点があります。
・ルール通りではありますが、こちらのメソッドをコールする時はMain Threadで行いましう。Main Threadでなければクラッシュします。
・このメソッドをコールした際再描画処理が同期実行されます。何が困るかというと、描画が終わるまでクライアント側の処理が止まってしまうため、アプリのパフォーマンス上不都合な事態を招く可能性もあります。
さらに、こちらはself.view上で呼ばなけれな制約更新->frame更新->描画ができないのでコールする場所にも注意したいですね。