明日までに解決!iOSアプリのカクツキをTime Profilerで撃退する方法

本投稿は TECOTEC Advent Calendar 2024 の23日目の記事です。

こんにちは!決済認証システム開発事業部の冨永です。

普段はiOSアプリ開発を中心に開発しています。

「スクロールがカクつく」「画面の動きが遅い」 そんなパフォーマンス問題に、時間がなくても即効性のある解決策を探していませんか?

iOSアプリのパフォーマンス改善で強力な味方になるのが「Time Profiler」です。 しかし、公式ドキュメントを読み込む時間や、手探りで設定を調整する余裕がないエンジニアも多いはず。

この記事では、切羽詰まった状況のエンジニアでも明日までに実践できるTime Profilerの使い方を解説します。 特に、SwiftUIをメインで開発しているエンジニアが直面しがちな「hanging」や「スクロールのカクツキ」を改善するための具体的な設定と分析方法をお伝えします。

※ 時間がある方は本記事よりもApple 公式が公開している以下の資料がおすすめです。 developer.apple.com

目次

前提条件

この記事では、以下の環境を前提に解説しています。

  • macOS Sonoma 14.7
  • Xcode 15.4
  • Instrumentsのバージョンもこれに準じたものを使用。

バージョンや環境によっては、一部UIや手順が異なる場合がありますのでご注意ください。

Time Profilerとは?

Time Profilerは、XcodeのInstrumentsに含まれるツールの一つで、 アプリのCPU使用率を可視化し、処理の遅い箇所を特定するために使います。 これにより、どの関数や処理がボトルネックになっているかを効率的に分析できます。

Time Profilerの開き方

Time Profilerは、以下の手順で簡単に起動できます。あなたの開発フローに合った方法を選んでください。

方法 1: Xcodeから直接起動する

  1. Xcodeでターゲットアプリをビルド。
  2. メニューから Product > Profile を選択。
  3. Instrumentsが起動するので、テンプレート一覧から Time Profiler を選択。

方法 2: XcodeのDeveloper Toolsから起動する

  1. Xcodeを開き、メニューから Xcode > Open Developer Tools > Instruments を選択。
  2. Instrumentsが起動するので、テンプレート一覧から Time Profiler を選択。

方法 3: Spotlightを使って直接起動する

  1. macOSのSpotlight検索(Cmd + Space)を開く。
  2. 「Instruments」と入力して選択。
  3. Instrumentsが起動したら、テンプレート一覧から Time Profiler を選択。

Time Profilerが便利な理由

  • CPU負荷の高い箇所を特定可能

アプリ内で最も多くCPUリソースを消費している処理を視覚化します。

  • 関数ごとの詳細分析

処理が遅い箇所をStack Traceで追跡でき、特定の関数やスレッドに焦点を当てられます。

  • リアルタイムで分析可能

実行中のアプリの挙動をリアルタイムでモニタリングできます。

Step 1: Time Profilerの基本設定

Time Profilerを効果的に活用するには、まず適切な設定を行うことが重要です。 このセクションでは、Time Profilerの初期設定と事前準備について解説します。

以下のポイントを押さえることで、Time Profilerの分析をスムーズに進められます。

1. リリースモードで計測

アプリを本番環境に近い動作で再現するため、リリースビルドで計測します。

  • デフォルトの場合。

Xcodeのデフォルト設定では、ProfileのConfigurationはすでにReleaseになっています。そのため、特別な設定を行う必要はありません。

  • カスタムスキームを使用している場合

カスタムスキームを使用している場合は、以下を確認してください。

  1. XcodeメニューからProduct > Scheme > Edit Schemeを選択。
  2. Profile構成を開き、Build Configurationが「Release」に設定されていることを確認。

※ もし別のConfigurationになっている場合は、Release用のConfigurationを選択してください。

※ Release用のConfigurationに本番用の設定が含まれており、Configurationを分けたい場合はテスト用のConfigurationのOptimization Levelを「Fastest, Smallest」に設定すればリリース用のコンパイラになるとは思っていますが、確証は得られていないです。

参考:Getting Started with Instruments - WWDC19 - Videos - Apple Developer 18:34 時点の動画参照

2. 実機で計測する

シミュレータではPCとリソースを共有しているため、正確な値が取れません。必ず実機で計測してください。 できれば、古い実機で。

参考:Getting Started with Instruments - WWDC19 - Videos - Apple Developer 20:13時点の動画参照

参考: Getting Started with Instruments - WWDC19 - Videos - Apple Developer 19:13時点の動画参照

3. Time ProfilerのHang検出の設定を変更する(任意)

Time Profilerでは、UIの遅延や描画のカクツキを特定するためにHang検出がデフォルトで有効になっています。

Hangの用語の意味についてはApple公式資料で以下のように述べられています。

個別のユーザー操作の処理の遅延が顕著になった場合、その応答しない期間は Hang と呼ばれます。 〜中略〜 Hangはほとんどの場合、メイン スレッドでの長時間実行作業の結果です。

参考:Understanding hangs in your app | Apple Developer Documentation

ただし、デフォルト設定では250ms以上のHangで反応するようになっており、詳細な分析には不十分な場合があります。 より小さな遅延を検出するには、以下の手順で設定を変更してください。

※ 前提条件に記載の環境での設定方法になります。その他の環境については設定方法が変わる場合がございます。

  • InstrumentsでTime Profilerを選択。
  • Fileタブ→ Recording Optionsを指定、
  • 設定画面で Options ForにHangsを指定し、Reporting Thresholdで「Include Brief Unresponsiveness (>100ms)」を設定

これにより、100ms以上の応答遅延を可視化できます。 ※この設定は後で変更しても問題ありません。

AppleがWWDC 2023の「Analyze hangs with Instruments」セッションで以下のように述べています。

UI要素を更新するたびにメインスレッドで多少の時間がかかるため、これらの更新が100ミリ秒以内に行われるようにする必要があります。理想的には、メインスレッドでの作業は100ミリ秒以上かかるべきではありません。より高速化できれば、さらに良いでしょう。メインスレッドでの長時間実行される作業も、ヒッチの原因となる可能性があります。

このことから、100msが理想的な基準であるといえます。 100ms~250msの範囲は状況次第では許容されますが、可能な限り100ms以内に収めるようにするのが良いでしょう。

参考:Analyze hangs with Instruments - WWDC23 - Videos - Apple Developer

4. Time ProfilerにSwift TaskとView Bodyを追加

後の分析をスムーズに進めるため、Time Profilerに以下の項目を事前に追加しておきましょう。

  • Swift Task

非同期処理のボトルネックを特定する際に役立ちます。

  • View Body

どのViewが遅れているかを視覚的に確認できます。

Timer Profiler右上の「+」を押下することで追加ができます。 これらを事前に設定することで、問題箇所の特定が格段に効率的になります。

Step 2: 検証端末の基本設定

Time Profilerを使用せずに、実機にHang検出モードを設定することで、UIのカクツキや遅延を可視化することが可能です。 また、時間が限られている場合でも、QAチームや他の開発メンバーと並行して調査を進めることで、効率的に問題を特定できます。

※Hang Detectionの設定はiOS 16以上で利用可能です。

設定手順

  1. iPhoneの設定アプリを開く。
  2. Developer > ハング検出 (Hang Detection) に進む。
  3. ハング検出を有効化をONにする。

この設定をチームで共有することで、QAチームや他の開発メンバーにもHangの発生箇所を並行して調査してもらうことが可能です。 これにより、問題箇所の特定を迅速に進められます。

参考:iOSアプリのパフォーマンス計測に入門してみた|NTT Resonant Technology

Step 3: ボトルネックを特定して対策を立てる

Time Profilerで得られるデータを基に、アプリのパフォーマンスを低下させている原因(ボトルネック)を特定し、適切な対策を講じます。このセクションでは、具体的な特定手順と、頻出する2つのパターンに応じた対応方法を解説します。

1. Time Profilerで計測する

  • Xcodeの画面から起動する場合

Ctrl + iを押してInstrumentsを起動し、Time Profilerを選択します。

  • Instrumentsから起動する場合

Instrumentsの左上で検証対象のアプリを選択します。

  • 計測開始 計測が始まりますので、検証したい操作を行います。操作が完了したら左上の■ボタンを押して計測を停止してください

2. ボトルネックの範囲を特定する

Time Profilerでは、計測後にクリック長押しで特定の範囲を選択できます。この機能を活用して、以下のポイントに着目しながら範囲を選択してください

  • Hangしている箇所

操作中にカクツキや遅延が発生している部分を選択します。

  • Main Threadで処理が多いところ

メインスレッドが過剰に負荷を受けている箇所は、UIの描画遅延や応答性の低下を引き起こす原因になります。

Time Profilerから該当アプリの「>」ボタンをクリックし、Main Threadを展開し、長時間Main Threadが使用されている箇所を特定する

  • Main Threadがブロックされているところ

あるいは、長期間MainThreadがブロックされている箇所を見つけます。

メイン スレッドが応答しなくなる主なケースは 2 つあります。 最も単純なケースは、メイン スレッドが他の作業でまだ忙しい場合です。この場合、メイン スレッドには大量の CPU アクティビティが表示されます。 もう 1 つのケースは、メイン スレッドがブロックされていることです。これは通常、メイン スレッドが他の場所で他の作業が完了するのを待機しているためです。スレッドがブロックされると、メイン スレッドでの CPU アクティビティはほとんどまたはまったく発生しません。

参考:https://wwdcnotes.com/documentation/wwdcnotes/wwdc23-10248-analyze-hangs-with-instruments/

  • CPU利用率が高いところ Time Profiler上でCPU使用率がピークに達している箇所を範囲選択します。

3. Stack Traceを確認する

Time ProfilerのStack Traceでは、実行された関数の詳細を確認できます。

しかし、そのままだと見づらい場合が多いため、Call Treeタブでフィルターを設定することで、重要な箇所を絞り込むことができます。

以下は、見やすくするための推奨設定です。

  • Separate by State:状態でログを分割
  • Separate by Thread:スレッドでログを分割(推奨)
  • Invert Call Tree:末端の関数 → 親の関数のツリーに変換
  • Hide System Libraries:システムライブラリを隠す(推奨)
  • Flatten Recursion:再帰関数をまとめる(推奨)
  • Top Functions:重い処理のみを表示(推奨)

これらのフィルター設定を活用することで、特にメインスレッドでの負荷が発生している箇所を効率的に分析できます。

4. ボトルネックの関数を特定する

Stack TraceからMainThreadというところをクリックします。 す。ここにMainThreadでのタスクが記録されており、クリックすると深掘りができます。 ※ Optionを押しながら開くと全て開くことができます。

Weightは呼び出し先も含めてかかった処理時間、パーセンテージで、 Self Weightは呼び出し先の処理を除いた処理時間になります。

Time Profilerの右ペインの下部に「Heaviest Stack Trace」セクションがあります。 ここには、計測中に実行された関数の負荷が重い順にリストアップされています。 この項目を使って重いタスクを特定していきます。

  1. 「Heaviest Stack Trace」内の関数を上から順に確認し、処理の重い関数を特定します。
  2. 必要に応じて、範囲選択で絞り込んだ時間帯に対応するスタックを確認します。

  3. 注意点

    • 負荷の高い関数は上位に表示されるため、効率的に分析できます。
    • グレーアウトされている関数や「thunk」と記載されている箇所は、システム処理であるため無視して構いません

5. 特定した関数に対して対応策をとる

特定した関数を見つけたら、安易にバックグラウンドにスレッドを実行するという選択肢を取ってはいけません。

Time Profiler上では処理が高速だが、呼び出し回数が多いパターンと、処理自体が遅いパターンの区別がつきません。

どちらのパターンなのかを考慮し対応する必要があります。

また、実行する処理が1ms以下の場合はバックグラウンドに移行することで逆にパフォーマンスが落ちる場合もあるとのことでした。

特定した関数がどのようにパフォーマンスに影響しているかを分析し、以下の2つのパターンに応じて対応します。

A: 処理は高速だが、呼び出し頻度が高い

  • 解決策
    • 処理回数を減らすため、呼び出しタイミングを最適化します。
    • バッチ処理の活用や、同じ計算を何度も行わないよう調整します。

B: 処理そのものが重い

  • 解決策
    • 重い処理をバックグラウンドスレッドに移行し、メインスレッドの負荷を軽減します。
    • 非同期処理を使用して、ユーザー体験を損なわないようにします。

検証を補助するテクニック

  1. os_signpostを使用する

関数にos_signpostを仕込むことで、呼び出し回数や処理時間を詳細に分析できます。 この情報を基に、A/Bどちらのパターンに該当するかを効率的に判断可能です。

  1. Time ProfilerでView BodyとSwift Taskを活用

    • View Body:SwiftUIの遅れている箇所を特定するために役立ちます。呼び出し回数や実行時間を割り出すことができます。

    • Swift Task:非同期処理の影響範囲を可視化し、問題箇所を絞り込みます。

まとめ

Time Profilerは、iOSアプリのパフォーマンスを効率的に改善するための強力なツールです。

この記事では、以下のステップを通じて、明日から実践可能な設定や分析手法をお伝えしました。

  1. Time Profilerの基本設定:リリースモードでの計測やSwift Taskの追加など、初期設定の重要ポイントを解説。
  2. 検証端末の設定:Hang検出モードを活用し、チーム全体で効率的に調査を進める方法を紹介。
  3. ボトルネックの特定と対策:Heaviest Stack Traceを活用して遅延箇所を特定し、適切な対策を実行する手順を解説。

これらの手法を活用することで、スクロールのカクツキやUIの遅延を短期間で改善する道筋が見えてくるはずです。

さらに深掘りしたい方は、公式ドキュメントやWWDCセッションも参考にしてみてください。

• Instrumentsチュートリアル

Profiling apps using Instruments | Apple Developer Documentation

• WWDC 2023 - Analyze hangs with Instruments

Analyze hangs with Instruments - WWDC23 - Videos - Apple Developer

テコテックの採用活動について

テコテックでは新卒採用、中途採用共に積極的に募集をしています。 採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。 ご興味を持っていただけましたら是非ご覧ください。 tecotec.co.jp