読者です 読者をやめる 読者になる 読者になる

なるようになるかも

力は多くの場合、その人の思いを超えない。

iOS6/7のviewDidLoadが呼ばれるタイミングの違い

viewDidLoadが呼ばれるタイミングは、UIViewControllerviewプロパティが初めてアクセスされたタイミングである」と公式ドキュメントに書いてあります。

しかし、 いつ・誰がviewプロパティにアクセスするのか についてはフレームワーク内部なのでブラックボックスとなっています(スタックトレースを追えばすぐ分かるんですけど)。

例えばこういう書き方をしたとします。

HogeViewController *vc = [HogeViewController new];
[self.navigationController pushViewController:vc animated:YES];
vc.hogeLabel.text = @"hoge";

このコードはiOS6ではラベルが書き換わるのだけど、iOS7ではvc.hogeLabelnilと評価されるため、動作しません。ここでは画面遷移をコードで管理していますが、storyboardを用いて、[segue destinationViewController]で取得した場合でも同じことが言えます。

つまるところ、iOS6ではpushViewController:animated:presentViewController:animated:が実行された時点でviewプロパティにアクセスされ、viewDidLoadがコールされていたのに対して、iOS7ではそのスタックを抜けた時点で初めてviewプロパティにアクセスされるよう変更されました。

遷移処理をその場で行わず、メインスレッドにキューイングするように変更されたのでしょうね。

こんなトラップにハマる人はまずいないと思うのですけど、イニシャライザやviewDidLoadで時間の掛かる処理(ネットワーク通信など)を行い、プログレス表示を行いたい場合、本来は非同期処理にしてコールバックを受け取るような仕組みを作る必要があります。

これを同期的に処理を書こうと考えたのか、

[self showProgress]; // keyWindowに対してプログレス用のviewをaddする
HogeViewController *vc = [HogeViewController new];
[self.navigationController pushViewController:vc animated:YES];
[self hideProgress]; // addしたviewをremoveする

と書いてあるコードが存在し、もはや期待した動作をしていませんでした。ていうかこんな発想自体ないわー。

バッドノウハウですが、

HogeViewController *vc = [HogeViewController new];
[self.navigationController pushViewController:vc animated:YES];
vc.view;
vc.hogeLabel.text = @"hoge";

と書いて無理やりviewDidLoadと呼びだすこともできます。ただしコンパイラの最適化や今後のiOSバージョンによって挙動が変わる恐れがありますし、適切に非同期処理を書くべきです。