MetricKit再構築:iOS 27の状態認識テレメトリ
AppleのWWDC 2026のデモアプリは、1日のアプリ利用全体で平均した結果として、毎秒15ミリ秒のスクロールヒッチ率を報告していました。ところがタブごとに分けると、同じデータがまったく異なる事実を語ります。一方のタブは1ms/s、もう一方は71ms/sだったのです。1 片方の画面はほぼ完璧で、もう片方はセッションの言葉を借りれば「深刻な中断を経験している」状態でした。1 混合された数値は、その両方の事実を覆い隠していたわけです。セッション222「Meet the new MetricKit」は、iOS 27がそのギャップをいかに埋めるかを語ります。フレームワークのAPI表面をゼロから作り直し、新しいコンパニオンフレームワークStateReportingによって、アプリ全体のフィールドメトリックを状態ごとのメトリックへと変換するのです。これでフィールドテレメトリは、すべてのパフォーマンスエンジニアが真っ先に問う疑問にようやく答えられるようになります。つまり「どの画面が遅いのか?」という問いです。
TL;DR
- iOS 27では、MetricKitは「文脈的に豊かで表現力のあるモダンなSwiftファーストAPIによってゼロから再構築」されており、このセッションで紹介される新機能はすべて新しいAPI専用です。1
- エントリポイントとなるのは
MetricManagerクラスです。アプリは起動時にmetricReportsとdiagnosticReportsという非同期ストリームをawaitします。どちらのレポート型もCodableに準拠しているため、JSONEncoderを使えばそのまま分析サーバーへ送信できます。1 - レポートは構造化されています。
intervalEntriesは1日全体のエントリに加えて、より細かな内訳を保持し、.cpu、.memory、.display、.gpuといったメトリックグループへと整理され、さらにpeakMemoryのような個々の値にまで分解されます。1 - iOS 27の新データとして、レンダリングパフォーマンスを測るMetalのフレームレートメトリック、メモリ上限による終了を捉えるメモリ例外診断、そして個々のクラッシュ診断をメトリックの傾向に結びつけるクラッシュの
categoryが追加されました。1 - 目玉となる機能は
StateReportingフレームワークです。アプリが置かれている状態(アクティブなタブ、実験の振り分け、ビューの構成など)を報告すると、MetricKitが状態ごとにメトリックを集約し、1つの混合された数値を画面ごとの内訳へと置き換えます。1
ゼロから再構築
MetricKitチームのエンジニア、Yonniが1:23からiOS 27の再構築を紹介します。
MetricKitの役割そのものは変わっていません。パフォーマンスワークフローにおける「収集の担い手」であり、2種類のデータを提供します。メトリックはあるパフォーマンス領域が全体として改善しているのか悪化しているのかを教えてくれ、診断はどのコードパスが問題を引き起こしたのかを教えてくれます。1 変わったのは、そのデータを受け取る仕組みのすべてです。セッションは率直にこう述べています。iOS 27ではフレームワークが「文脈的に豊かで表現力のあるモダンなSwiftファーストAPIによってゼロから再構築」されており、「本日お話しするすべての進化は、この新しいAPI群の専用機能です」。1
新しいエントリポイントはMetricManagerクラスです。デリゲートを登録してペイロードを解析する代わりに、metricReportsプロパティを非同期ストリームとしてawaitし、レポートを受け取ります。セッションからは2つの運用ルールがそのまま導き出せます。1つは「購読の遅延によるデータ損失を避けるため」セットアップをアプリ起動時に行うこと、もう1つは「後続のデータが準備でき次第ストリームがレポートを配信し続けられるよう」MetricManagerを生かし続けることです。1 Appleは、アプリ起動直後にこの処理をデタッチされたタスクや専用のサービスクラスで実行することを推奨しています。1
セッションではコードがスライド上で提示されているため、以下のスニペットはその説明に合致する呼び出しの形を示した参考例です。出荷前にはApple公式ドキュメントで正確なシグネチャを確認してください。
// Illustrative call shape based on session 222; verify against the docs.
let manager = MetricManager()
Task.detached {
for await report in manager.metricReports {
// Encode and ship, or inspect specific groups.
}
}
かつてレポートをサーバーへ送信するには、不透明なペイロードデータを扱う必要がありました。今ではMetricReport値がCodableに準拠しています。「JSONEncoderを作成して、レポート全体をエンコードするだけです」。1 ドキュメント全体ではなく特定の値だけが欲しい場合も、レポートは完全に構造化されています。intervalEntriesを反復処理しますが、これには「1日全体を集約したエントリと、利用できる場合はより小さな内訳ウィンドウが含まれ」、それぞれは通常数時間程度で、その期間にメトリックが存在する場合にのみ提供されます。1 各インターバルの内部では、メトリックがメトリックグループへと整理されており、「各グループは.cpu、.memory、.display、.gpuのようにシステムのある側面を表します」。1 関心のあるグループまで絞り込み(セッションの例ではmemoryMetricsを取り出しています)、メトリックのケースをswitchで分岐させて、peakMemoryのような個々の値にたどり着きます。1
メトリックのカタログもiOS 27で拡充されます。起動時間のヒストグラム(セッションの例では大半の起動が510〜540ミリ秒に収まっています)、ハング、アニメーションメトリック、そしてCPU・GPU・ディスク書き込み・ネットワーク転送といったリソース消費に加えて、MetricKitはMetalのフレームレートメトリックを追加します。セッションはフレームレートを「ゲーム開発者がレンダリングパフォーマンスを理解するうえで重要なメトリック」と呼び、最適化の側面については「Find and fix performance issues in your Metal game」を参照するよう案内しています。1
起動の計測を自前で実装するのではなく、MetricKitの起動メトリックに頼りましょう。Appleはユーザーがアプリのアイコンをタップした瞬間から最初のフレームが描画されるまでの時間を起動時間として計測します。これはあなたのプロセスが存在し始めるよりも前のことです。3 手作りのタイマーはコードが動き始めて初めて計測を開始できるため、main前のウィンドウをまったく取りこぼし、ユーザーが実際に体感する起動時間を過小に見積もってしまいます。肝心な部分を捉えられないタイマーを作るのではなく、MetricKitが提供するヒストグラムを読み取りましょう。
診断:バックトレース、メモリ例外、クラッシュカテゴリ
メトリックは何かが後退したことを教えてくれ、診断はそれがどこで起きたのかを教えてくれます。クラッシュやハングのように何か問題が起きると、「システムがデバイス上で診断を取得し」、診断レポートが「その詳細をパッケージ化して、MetricKitを通じてただちにアプリへ届けます」。1 多くの診断には、イベント発生時点の正確なコールスタックを示すバックトレースが含まれています。セッションのウォークスルーでは、シンボル化されたバックトレースがシステムコードのスレッド開始から始まり、アプリへと入り、アプリのsubmitReport()関数で止まります。これが失敗地点であり、修正を狙うべき場所を示しているのです。1
クラッシュ診断には、バックトレース、終了理由、例外タイプが含まれます。iOS 27の新機能として、終了のcategoryが「各クラッシュがメトリックの中でどのように計上されたかを示す」ため、「異常終了が増加傾向にあるとき、それを個々の診断と直接相関づけられます」。1 ダッシュボード上のメトリックの推移と、その背後にある個々のクラッシュレポートが、ようやく共通のキーを持つようになるわけです。
iOS 27はメモリ例外診断も追加します。「アプリやエクステンションがメモリ上限を超えて終了させられたとき、何が起きたのかについてより深い洞察が得られます」。1 エクステンションが明示的に対象に含まれている点は、ウィジェットやエクステンションのメモリキルをリモートからデバッグしているすべての人にとって重要です。
データの受け取り方はメトリック側と同様です。MetricManagerインスタンスでdiagnosticReportsをawaitし、こちらもアプリ起動時にデタッチされたタスクやサービスクラスで処理します。DiagnosticReport値もCodableに準拠しており、同じエンコードして送信するパイプラインに乗せられます。1 レポートが構造化されているため、診断のケースをswitchで分岐できます。クラッシュのケースではバックトレース・理由・categoryが得られ、ハングのケースは別の処理へ振り分けられます。1
// Illustrative call shape based on session 222; verify against the docs.
for await report in manager.diagnosticReports {
switch /* diagnostic case */ {
case /* crash */: break // backtrace, reason, category
case /* hang */: break // handle separately
default: break
}
}
StateReporting:1つの混合数値から画面ごとの真実へ
ここまでの内容はいずれもアプリ全体のテレメトリを説明していますが、アプリ全体のテレメトリには限界があります。セッションの経費精算アプリが、その問題を具体的に示してくれます。このアプリは機能をReportsタブとSpendingタブに整理しています。1日を通して、MetricKitは5分間のスクロールに対して合計4.5秒のヒッチ時間、つまり15ms/sのヒッチ率を報告します。しかしこの数値は「ReportsタブとSpendingタブを行き来していたとしても、アプリ利用全体にわたって平均化されたスクロールヒッチ率」にすぎません。1 アプリがヒッチしていることは分かります。けれどもどこでヒッチしているのかは分かりません。
新しいStateReportingフレームワークは、その混合を取り除きます。状態とは「MetricKitがそれらの特性に応じてメトリックを集約できるよう、アプリの構成や挙動を記述するために自分で定義する情報」です。1 ユーザーがタブ間を移動するたびに、アプリは各遷移を報告し、MetricKitはそれらの状態をメトリックや診断データと突き合わせます。1
デモでの成果こそが、この再構築を正当化する瞬間です。1つの混合された15ms/sという数字の代わりに、メトリックが状態ごとに届きます。Spendingタブは1ms/sで「驚くほどスムーズ」にスクロールしていた一方、Reportsタブは「71ms/sまで急上昇していた」のです。1 セッションは、混合された数値では決して支えられない結論を導きます。「Spendingタブは素晴らしい性能を発揮しています! ところがReportsタブは深刻な中断を経験しており、まさにそこにこそ最適化の労力を集中させるべきなのです」。1 1つの数字が、判定であり作業指示書へと変わったのです。
状態は、区間で囲い込むのではなく、遷移モデルに従います。「開始と終了のペアは存在せず、アプリは任意の時点で自分が置かれている条件を報告します」。そしてMetricKitは、アプリが各状態にとどまった時間の長さを追跡します。1
ドメイン、メタデータ、状態ごとのエンコーディング
各状態はドメインにスコープされ、ドメインは「アプリの機能や領域を記述」します。1つのドメインは同時に1つのアクティブな状態しか保持できませんが、別々のドメインを使えば複数の状態を同時に進行させられます。1 セッションの例はA/B実験です。実験的な変更をオンにすると経費はデータベースから小さなバッチで取得され、オフにするとより大きなバッチで取得されます。タブの状態とバッチサイズの状態を別々のドメインに置くことで、「MetricKitは各タブと各バッチサイズについて別々のメトリックを配信します」。1 同じパイプラインから、画面ごとのテレメトリと実験の読み取り結果を、しかもフィールドで得られるのです。
セッションでは導入の手順が3つに分かれています。StateReportingフレームワークをインポートし、ドメイン(「通常は逆引きDNS形式の文字列」)を作成してMetricManagerインスタンスのセットアップ時に登録し、アプリが各状態に入るときに遷移を報告します。たとえば「Reports」という文字列で識別される状態への遷移を報告するわけです。1 さらに細かい粒度が必要なら、ReportableMetadataマクロを使って独自のstructを定義し、そのメタデータ型でStateReporterを作成し、ラベルと独自の型の両方を添えて遷移を報告します。セッションのViewConfigurationの例では、listSizeの値と、リストがソート済みかどうかを保持しています。1 ここでも繰り返しますが、セッションはこの流れを完全なシグネチャなしでスライドで示しているため、この形はコピーすべき構文ではなく、ドキュメントで確認すべきものとして扱ってください。
受け取る側では、レポートに2つ目の軸が加わります。いずれの状態もまだ報告されていない段階では、メトリックレポートのstateEntriesプロパティは空です。導入後、レポートはStateEntry値を保持し、それぞれが「その個々の状態で過ごした時間にわたって集約されたメトリック値」を保持します。1 サーバーパイプライン向けには、エンコードされた出力をドメインごとにグループ化できます。JSONEncoderのuserInfoプロパティにencodingFormatKeyキーを設定してbyStateReportingDomainを指定すると、エンコードされたレポートは状態エントリとインターバルエントリの両方を、「レポート内に存在する各ドメインと状態ごとにグループ化して」提示します。1
ベストプラクティスと、どこから始めるか
セッションは、苦労して得たスキーマ設計の助言のように読める指針で締めくくられます。ドメインは1つのアプリ領域に絞って狭くスコープすべきです。状態遷移は「一時的なUIイベントではなく、安定した意味のあるフェーズを表すべき」です。1 各状態は、後退が現れたときに、その状態だけで修正の的を絞れるだけの情報が得られるように設計しましょう。そして何でも計測したくなる衝動は抑えてください。「状態が多すぎると、データが細かくなりすぎて、かえって全体像を読み解きにくくなることがあります」。また、オーバーヘッドを最小化するために状態数には上限が設けられています(セッションは具体的な数値を示していません)。1 出荷前には、報告された状態が想定どおりかをPoints of Interestインストゥルメントで検証しましょう。1
カーディナリティは、メタデータ側でも同じ落とし穴です。頻繁に変化する状態の値は、正確な件数を報告するのではなく、粗いカテゴリ(small、medium、large)にバケット化しましょう。AppleのWWDC 2026パフォーマンスラボでは、チームがMetricKitの採用やより広範な電力ワークフローについてライブで質問に答えていましたが(その様子はWWDC26ラボでAppleのパフォーマンスチームが語ったことに記録しています)、そこでチームが指摘したのは、「1,000件と1,001件」を記録しても洞察を生まずにコストだけが増える、という点でした。この2つの値は同じパフォーマンス領域に収まるため、それぞれに個別の状態を設けてもオーバーヘッドが増えるだけで何も得られないのです。4 挙動が変わる境界を選び、その間にあるものはすべてまとめてしまいましょう。
収集側はシステムの半分にすぎません。セッションは「すべてのデバイスにわたってメトリックを分析することはデータサイエンスの問題です」と率直に述べています。レポートを取り込むサーバーを立ち上げ、関心のある次元に沿って集約し、ベースラインを確立して、どちらの方向への変動も監視するのです。1 CodableなレポートとbyStateReportingDomainエンコーディングは、まさにそのパイプラインに供給するために存在しています。
既存の採用者に向けた締めくくりの指示は明確です。「MXMetricManager APIを使っているなら、これらすべての新機能を活用するために新しいMetricManager APIへ移行してください」。1 Appleのドキュメントは現在その移行を正式化しており、MXMetricManagerは27.0で非推奨と記され、「代わりにMetricManagerを使用してください」という案内が添えられています。2 同じ段階的な27境界での適用は、このサイクルの他の箇所でも見られました。Image PlaygroundのiOS 27におけるImageCreatorの非推奨化もその一例で、そこでは非推奨の警告が、公開リリースで完全な破壊的変更へと切り替わります。新しいAPIこそが、セッションのすべての進化が宿る場所であり、セッションはそれらを「フレームワークの未来」として提示しています。1
FAQ
iOS 27でMetricKitは実際に何が変わったのですか?
フレームワークがモダンなSwiftファーストAPIで再構築されました。エントリポイントは新しいMetricManagerクラスで、メトリックレポートと診断レポートはawait可能な非同期ストリーム(metricReports、diagnosticReports)として届きます。レポートはCodableに準拠しており、JSONへ直接エンコードできます。また、その構造はintervalEntriesやメトリックグループを通じてコード上でたどれます。iOS 27はさらに、Metalのフレームレートメトリック、メモリ例外診断、クラッシュ診断をメトリックの計上に結びつけるクラッシュのcategory、そして状態ごとのメトリックを実現するStateReportingフレームワークも追加します。1
StateReportingはどのメトリックがどの状態に属するかをどう判断するのですか?
アプリが遷移、つまり自分が定義したドメインの中で移動先の状態を報告します。MetricKitはアプリが各状態にとどまった時間を追跡し、そこで過ごした時間にわたってメトリック値を集約します。開始と終了のペアはなく、アプリは任意の時点で置かれている条件をただ報告するだけです。その後、各状態はメトリックレポート内に固有のStateEntryを得ます。1
画面と実験の振り分けのように、複数の次元を同時に追跡できますか?
はい。各ドメインは同時に1つのアクティブな状態しか保持できませんが、別々のドメインは並行して動作します。セッションの経費アプリは、アクティブなタブを1つのドメインに、データベースのバッチサイズ実験を別のドメインに置いており、MetricKitは各タブと各バッチサイズについて別々のメトリックを配信します。1
すべてのUIイベントを状態として報告すべきですか?
いいえ。セッションが推奨するのは、一時的なUIイベントではなく安定した意味のあるフェーズを表す状態であり、1つのアプリ領域に絞って狭くスコープしたドメインであり、そして全体としての節度です。状態が多すぎるとデータは読み解きにくくなり、システムはオーバーヘッドを最小化するために状態数に上限を課します。出荷前には、Points of Interestインストゥルメントで状態を検証しましょう。1
MXMetricManagerから移行しなければならないのですか?
セッションの指針は、MXMetricManagerから新しいMetricManager APIへ移行することです。なぜなら、取り上げられた新機能(非同期ストリーム、Codableなレポート、状態認識メトリック、新しいメトリックおよび診断の各型)はすべて、新しいAPI群の専用機能だからです。1
MetricKitは、今年の2部構成の物語のうちフィールド側を担っています。Instrumentsはラボの中でヒッチを見せてくれ、状態認識のMetricKitは実際のユーザーにとってどの画面がヒッチするのかを教えてくれます。ラボ側からの解説はInstruments 27とアプリの応答性で扱っています。71ms/sのタブを実際に修正するレンダリング作業はiOS 27におけるSwiftUIのパフォーマンスと相互運用にあります。そして、そもそも混合された平均値が人を誤らせる理由はパフォーマンスの死角のテーマです。シリーズ全体のハブはApple Ecosystem Seriesです。
References
-
Apple, WWDC 2026 session 222, Meet the new MetricKit. Source for the iOS 27 ground-up rebuild and Swift-first API framing, the
MetricManagerentry point and themetricReports/diagnosticReportsasync streams,Codablereports andJSONEncoderusage,intervalEntriesand metric groups (.cpu,.memory,.display,.gpu,peakMemory), the Metal frame rate metric, memory exception diagnostics, the crash terminationcategory, thesubmitReport()backtrace walkthrough, theStateReportingframework (domains, transition model,StateReporter,ReportableMetadata,stateEntries,byStateReportingDomainviaencodingFormatKey), the expense-app demo numbers (15 ms/s blended; 1 ms/s Spending tab versus 71 ms/s Reports tab), the state best practices and Points of Interest validation, and the guidance to migrate fromMXMetricManagertoMetricManager. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation:
MXMetricManager. Marked deprecated as of 27.0, with the guidance “Use MetricManager instead.” ↩ -
Apple Developer Documentation: Reducing your app’s launch time. Launch time is measured from the moment the user taps the app icon to the first frame drawn, before the app’s process exists. ↩
-
Apple, WWDC 2026 performance group lab, session 8003. Paraphrased from a locally transcribed recording; no official transcript is published. The team advised bucketing fast-changing state metadata into coarse categories, noting that recording “1,000 versus 1,001 items” adds cost without insight. ↩