UIViewControllerのライフサイクルについて
UIViewControllerのライフサイクルについてまとめてみました。いざまとめてみるとviewの読み込みや表示に関する部分はある程度理解していたものの、レイアウト関連の処理については理解が甘かったことを認識しました。まとめてみたものの、あまり自信がありません(引用部分は基本的にリファレンスから引っ張ってきています)。
ライフサイクル
アクティビティ図
ざっくりとまとめると、だいたいこんな感じ。
Autolayout
使用時に結構つまずいたのがviewWillLayoutSubviews
、viewDidLayoutSubviews
なので、レイアウトはこの辺でやってるんだ〜くらいを押さえられれば良いと思います。
loadView
, viewDidLoad
読み込み系ライフサイクルメソッド(と勝手に名付けた)。
loadView
nib
=xib
などを使用しない場合にカスタムViewの初期化を行う箇所。
- (void)loadView; // This is where subclasses should create their custom view hierarchy if they aren't using a nib. Should never be called directly.
Storyboardやxibなど、InterfaceBuilderを利用している場合はこのメソッドを呼び出してはならない。
If you use Interface Builder to create your views and initialize the view controller, you must not override this method.
viewDidLoad
ViewController
のviewがロードされた後に呼び出される。
上記の通り、InterfaceBuilderを使用している場合はサブビューのセットアップは一般的にここで行うことになる。
- (void)viewDidLoad; // Called after the view has been loaded. For view controllers created in code, this is after -loadView. For view controllers unarchived from a nib, this is after the view is set.
viewWill~, viewDid~系メソッド
viewWill~
、viewDid~
ともに3種類存在する。
viewWillAppear:
, viewDidAppear:
表示系ライフサイクルメソッド(と勝手に名付けた)。
- (void)viewWillAppear:(BOOL)animated; // Called when the view is about to made visible. Default does nothing
- (void)viewDidAppear:(BOOL)animated; // Called when the view has been fully transitioned onto the screen. Default does nothing
viewWillAppear:
はviewが表示される直前に呼ばれる。
viewDidAppear:
は完全に遷移が行われ、スクリーン上に表示された時に呼ばれる。
注意点としてはviewDidAppear:
が呼ばれたタイミングではすでに画面に描画されている状態なので、このタイミングでviewの生成などを行うと当然おかしなことになる。
viewWillDisappear:
, viewDidDisappear:
非表示系ライフサイクルメソッド(とこちらも勝手に名付けた)。
表示系ライフルメソッドと対になるもの。
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing
- (void)viewDidDisappear:(BOOL)animated; // Called after the view was dismissed, covered or otherwise hidden. Default does nothing
viewWillDisappear:
はviewが表示されなくなる直前に呼び出される。
viewDidDisappear:
は完全に遷移が行われ、スクリーン上からViewController
が表示されなくなったときに呼ばれる。
注意点としては、viewDidDisappear:
が呼び出されたからといってviewController
オブジェクトが破棄されるわけではないということ。
例えばUITabBarController
やUINavigationController
などに保持され続けている場合はviewController
は保持される。
viewWillLayoutSubviews
, viewDidLayoutSubviews
レイアウト系ライフサイクルメソッド(勝手に名付け(ry
これらの動きを理解するにはUIView
のレイアウト処理について理解しておかなければならないので、一旦そちらをまとめる。
UIView
のレイアウトについて
たくさんあるけど、ひとまず以下の2つにしぼります。
- (void)updateConstraints NS_AVAILABLE_IOS(6_0); // Override this to adjust your special constraints during a constraints update pass
- (void)layoutSubviews; // override point. called by layoutIfNeeded automatically. As of iOS 6.0, when constraints-based layout is used the base implementation applies the constraints-based layout, otherwise it does nothing.
updateConstraints
は制約の更新のために呼ばれ、layoutSubviews
はレイアウト処理のために呼ばれる。カスタムの制約を付けたい、あるいは削除したい場合はupdateConstraints
をオーバーライドする。
順番としては「制約の更新→レイアウト更新→描画」となるのでupdateConstraints
→layoutSubviews
の順番で呼ばれる。
layoutSubviews
はsetNeedsLayout
あるいはlayoutIfNeeded
を使用して呼び出すこと。直ちに呼び出したい場合はlayoutIfNeeded
を呼び出すこと(リファレンスを呼んだ限りupdateConstraints
を直接呼び出してはいけないという記述はなかったが、updateConstraintsIfNeeded
、setNeedsUpdateConstraints
というメソッドが用意されているので使った方が無難そう)。
UIViewController
のレイアウト系メソッドについて
ViewController
の話に戻り、UIView
のレイアウト系メソッドを踏まえた上でこちらを読んでみる。
1.View Controllerのビューが新しい大きさに変わります。
2.自動レイアウト処理が無効であれば、自動大きさ調整マスクに従ってビューの大きさが変わります。
3.View ControllerのviewWillLayoutSubviewsメソッドが呼び出されます。
4.ビューのlayoutSubviewsメソッドが呼び出されます。ビュー階層を構築するために自動レイアウト機能が使われていれば、次の手順でレイアウト制約を更新できます。
a. View ControllerのupdateViewConstraintsメソッドが呼び出されます。
b. UIViewControllerクラスのupdateViewConstraintsメソッドは、処理の過程でビューのupdateConstraintsメソッドを呼び出します。
c. レイアウト制約の更新後、新しいレイアウトを計算し、ビューの位置を調整し直します。
5.View ControllerのviewDidLayoutSubviewsメソッドが呼び出されます。
引用:https://developer.apple.com/jp/devcenter/ios/library/documentation/ViewControllerPGforiOS.pdf
viewWillLayoutSubviews
はviewController
のview
のlayoutSubviews
メソッドが実行される前に呼ばれる。
updateViewConstraints
は
viewDidLayoutSubviews
はviewController
のview
のlayoutSubviews
メソッドが実行された後に呼ばれる。
// Called just before the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. The default is a nop.
- (void)viewWillLayoutSubviews NS_AVAILABLE_IOS(5_0);
// Called just after the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. The default is a nop.
- (void)viewDidLayoutSubviews NS_AVAILABLE_IOS(5_0);
/* Base implementation sends -updateConstraints to the view.
When a view has a view controller, this message is sent to the view controller during
the autolayout updateConstraints pass in lieu of sending updateConstraints directly
to the view.
You may override this method in a UIViewController subclass for updating custom
constraints instead of subclassing your view and overriding -[UIView updateConstraints].
Overrides must call super or send -updateConstraints to the view.
*/
- (void)updateViewConstraints NS_AVAILABLE_IOS(6_0);
dealloc
これはUIViewController
のメソッドではなくオブジェクトが解放されるときに呼ばれるメソッド。
典型的な使い道としてはNSNotificationCenter
を利用した通知登録解除、などを行う。delegate
の解除などを行う。
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:HOGE_KEY
object:nil];
self.tableView.delegate = nil;
}
遷移の構造によってはviewDidDisappear:
が呼ばれたからといってオブジェクトが破棄されるわけではないので、上記のようなdelegate
の破棄や通知センターの解除を行ってしまうと困る可能性がある。
例えばTabBarController
のような場合は、画面に表示されなくなってもそこに追加されているViewController
オブジェクトは破棄されないままなので注意。
残りがちなDeprecatedメソッド
viewWillUnload
とviewDidUnload
はもうなくなっているので、アプリがサポートしていないOSのソースに残っている場合は消してしまいましょう。
- (void)viewWillUnload NS_DEPRECATED_IOS(5_0,6_0);
- (void)viewDidUnload NS_DEPRECATED_IOS(3_0,6_0); // Called after the view controller's view is released and set to nil. For example, a memory warning which causes the view to be purged. Not invoked as a result of -dealloc.
まとめ
タイミングを間違えるとバグの温床になるので、ViewControllerのタイミングについてはしっかりと押さえたいところです。
ただし、冒頭でも書きましたが、viewWillLayoutSubviews
、viewDidLayoutSubviews
などのレイアウト周りはあまり自信がないので間違いがあれば指摘していただけると助かります。
今回は省略していますが、UIView
のwillMoveToSuperview:
、didMoveToSuperview
などのライフサイクルについても覚えておくとそのうち役に立つと思います。