TL;DR
-
Xcode7でiOS8系をサポートしたアプリを作成するときに、Interface Builder上(xibやstoryboard)で
UILabel
やUITextView
のフォントにHiraginoSans
を利用するViewControllerを作成すると、その画面ヘの遷移が遅くなる- 体感では、1つの
HiraginoSans
フォントを利用したUIパーツを配置する毎に1秒弱ずつ遅くなる - 現象が発生するのはアプリ初回起動時のみ
- 体感では、1つの
-
これはフォントに依存する問題で、System FontやTimes New Romanなど他のフォントを利用すると再現しなかった
-
Times Profilerで時間のかかるメソッドを特定した所、iOS8系のアプリ初回起動時には
[UINibDecoder decodeObjectForKey:]
というメソッドが呼ばれ、これがHiraginoSans
フォントの時だけ極端に遅いことが原因の模様 -
この問題は、以下の条件の時に再現
- UIパーツをxib / storyboardで配置
- 開発言語をObjective-C / swiftで実装
- 文字列は日本語 / 英語関係なし
経緯
業務中、とあるPull Requestをレビューしていた際に動作確認を行っていた所、「あるViewController(Objective-C)から別のViewController(swift)へ遷移しようとするとき、遷移前に固まったままページの読み込みが極端に遅くなる」、という現象に出くわしました。
現象が発生したのはiPhone5s iOS8.4のシミュレータでした。iOS9.1のシミュレータではすんなり表示されるという具合に。
原因を調査するため、Xcodeに標準で付属しているTime Profilerというツールを利用して調べてみました。
なお、このエントリの実行環境は以下の通りです。会社・自宅どちらの環境でも再現しました。
- Xcode: 7.1.1(会社) / 7.2(自宅)
- Mac OS: OSX Yosemite(会社) / OSX El Capitan(自宅)
Time Profilerとは
Time Profilerとは、その名の通りプロジェクト内の処理の実行時間を計測できるプロファイリングツールです。
Xcode->Open Developer Tool->Instrumentsから、Time Profilerのアイコンを選択すると起動できます。
アイコンを選択すると、以下の様な画面が表示されます。
スクリーンショットにあるように、Call Treeを辿ってメソッド単位で実行時間を確認できるのでこういったケースの原因分析にとても便利です。
原因となるメソッドの特定
今回の場合、
- iOS8.4では遅い
- iOS9.1では違和感を感じない(早い)
という状況であったため、特定のメソッドのOS間での挙動の違いであるだろうという推測のもと、Time Profilerを利用してiOS8.4で時間がかかっていた部分の特定を行いました。
試行錯誤を行って柔軟にチェックできるようにするため、サンプルプロジェクトを作成して検証することにしました。
今回作成したのは、以下の様な簡単なアプリです。
- 2枚のstoryboardと、それに対応するViewControllerのファイルを作成
- 初期表示されるstoryboardにボタンを置き、クリックするともう1枚のstoryboardへ遷移する
- 遷移後の画面に
UITextView
を置き、日本語の適当な文字列を入れる- (元々の現象では
UILabel
で発生していましたが、検証のため別のUIKitを利用することに)
- (元々の現象では
- レビューしていたPRと状況を近づけるため、フォントをPRで設定されていた
HiraginoSans-W6
に設定
このサンプルアプリはGithubに上げているので、気になる方は検証してみてください。
この状態でアプリを起動し、HiraginoSans-W6
を配置したstoryboardへ遷移しようとすると、PRで発生していた現象と同じく、遷移前の画面で固まりました。
固まった原因を探るべく、Time Profilerで時間の掛かったメソッドを特定します。
なんと、 [UITextView initWithCoder]
内で呼ばれていた[UINibDecoder decodeObjectForKey:]
の結果が1105.0msもかかっています。
この現象がどういった条件で遅くなるか
今回のような UILabel
や UITextView
を利用している画面は他にもたくさんあり、そういった画面では再現しなかったため、どういう条件でこの現象が発生するのか、サンプルプロジェクトをいじりながら検証しました。
他のフォントを置いた場合
HiraginoSans-W6
以外のフォントを数種類試してみました。
フォント名 | 検証結果 |
---|---|
HiraginoSans-W6 |
×(遅い) |
HiraginoSans-W3 |
×(遅い) |
Helvetika |
○(早い) |
Arial |
○(早い) |
Times New Roman |
○(早い) |
このように、他のフォントでは再現しなかったため HiraginoSans
特有の問題のようです。
恒常的に遅いのかどうかの検証
HiraginoSans
フォントを設定した状態で、サンプルアプリの当該現象が発生する画面へアプリ起動後何回か遷移させてみました。
すると、初回と2回目以降で、画面遷移の時間が体感できるレベルで異なりました。
Time Profilerを見てみます。
アプリ起動後、初回の画面遷移
アプリ起動後、2回目以降の画面遷移
なんと、2回目以降の画面遷移では [UINibDecoder decodeObjectForKey:]
が呼ばれていません。
このため、アプリ起動後、初回の画面遷移のみ起こる現象であることがわかりました。
その他の検証
現象の再現条件が分かったところで、更に以下についても検証しました。
swiftの実装が原因なのか
Objective-Cで同様のサンプルアプリを作りましたが同じ結果になりました。
つまり言語によらない問題のようです。
storyboardだけの問題か
PRのリポジトリ内で HiraginoSans
で検索をかけた所、別画面のxibでも利用されていることがわかりました。
実際にiOS8のシミュレータで走らせてみましたが遅いです。
このため、storyboard / xibに依存しない問題のようです。
iOS9系ではどうなのか
サンプルアプリをiOS9.1で実行した所、初回起動時にも[UINibDecoder decodeObjectForKey:]
が呼ばれていません。
OSのメジャーバージョンによってUIKitの処理が変わったのではないかと思われます。
【追記】実機ではどうなるか
iPhone5s, iOS8.4の手元の実機で検証しておりましたが、シミュレータよりは高速に処理されるようです。
しかしながら、フォントの利用の仕方によっては体感で分かるレベルで遷移に時間がかかりました。
結論
というわけでタイトル通りですが、iOS8系をサポートするなら、UILabel
やUITextView
のフォントに HiraginoSans
は使わないほうが良さそうです。
HiraginoSans
だけ何故遅くなるのか、原因についてはこのエントリの中では特定できませんでしたが、皆様のiOSアプリ実装の際の参考にしていただけますと幸いです。