UIViewControllerのライフサイクル

# UIViewControllerのライフサイクルについて

UIViewControllerのライフサイクルについてまとめてみました。いざまとめてみるとviewの読み込みや表示に関する部分はある程度理解していたものの、レイアウト関連の処理については理解が甘かったことを認識しました。まとめてみたものの、あまり自信がありません（引用部分は基本的にリファレンスから引っ張ってきています）。

## ライフサイクル

### アクティビティ図

ざっくりとまとめると、だいたいこんな感じ。

![ViewControllerのライフサイクル.png](https://qiita-image-store.s3.amazonaws.com/0/45525/670d0038-6f03-095f-ca22-90c510f8babf.png)

`Autolayout`使用時に結構つまずいたのが`viewWillLayoutSubviews`、`viewDidLayoutSubviews`なので、レイアウトはこの辺でやってるんだ〜くらいを押さえられれば良いと思います。

### `loadView`, `viewDidLoad`

*読み込み系ライフサイクルメソッド*（と勝手に名付けた）。

#### `loadView`

`nib`=`xib`などを使用しない場合にカスタムViewの初期化を行う箇所。

```objc
- (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を使用している場合はサブビューのセットアップは一般的にここで行うことになる。

```objc
- (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:`

*表示系ライフサイクルメソッド*（と勝手に名付けた）。

```objc
- (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:`

*非表示系ライフサイクルメソッド*（とこちらも勝手に名付けた）。
表示系ライフルメソッドと対になるもの。

```objc
- (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つにしぼります。

```objc
- (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`メソッドが実行された*後*に呼ばれる。

```objc
// 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`の解除などを行う。

```objc
-(void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:HOGE_KEY
                                                  object:nil];

    self.tableView.delegate = nil;
}
```

遷移の構造によっては`viewDidDisappear:`が呼ばれたからといってオブジェクトが破棄されるわけではないので、上記のような`delegate`の破棄や通知センターの解除を行ってしまうと困る可能性がある。

例えば`TabBarController`のような場合は、画面に表示されなくなってもそこに追加されている`ViewController`オブジェクトは破棄されないままなので注意。


## 残りがちなDeprecatedメソッド

`viewWillUnload`と`viewDidUnload`はもうなくなっているので、アプリがサポートしていないOSのソースに残っている場合は消してしまいましょう。

```objc
- (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`などのライフサイクルについても覚えておくとそのうち役に立つと思います。