← すべての記事

MetricKitの再構築:iOS 27の状態を意識したテレメトリ

AppleのWWDC 2026のデモアプリは、1日の使用全体を平均して、1秒あたり15ミリ秒というスクロールヒッチ率を報告しました。ところが同じデータをタブごとに分けてみると、まったく別の物語が見えてきます。一方のタブは1 ms/s、もう一方は71 ms/sだったのです。1 一方の画面はほぼ完璧で、もう一方はセッションの言葉を借りれば「深刻な中断を経験している」状態でした。1 平均値はその両方の事実を覆い隠していたのです。セッション222「Meet the new MetricKit」は、iOS 27がこのギャップをどう埋めるのかを語っています。フレームワークのAPI全体をゼロから作り直し、さらに新しいコンパニオンフレームワークStateReportingによって、アプリ全体のフィールドメトリクスを状態ごとのメトリクスへと変換します。フィールドテレメトリは、すべてのパフォーマンスエンジニアが真っ先に問う質問にようやく答えられるようになりました。つまり「どの画面が遅いのか」という問いです。

TL;DR

  • iOS 27では、MetricKitは「コンテキストが豊かで表現力のあるモダンなSwiftファーストのAPIとしてゼロから再構築」されており、セッションで紹介される新機能はすべて新しいAPIでのみ利用できます。1
  • 入口となるのはMetricManagerクラスです。アプリは起動時にmetricReportsdiagnosticReportsという非同期ストリームをawaitします。どちらのレポート型もCodableなので、JSONEncoderを使えばそのまま分析サーバーへ送信できます。1
  • レポートは構造化されています。intervalEntriesは1日分のエントリに加えてより小さな内訳を保持し、.cpu.memory.display.gpuといったメトリクスグループに整理され、さらにpeakMemoryのような個々の値まで掘り下げられます。1
  • iOS 27の新しいデータとしては、レンダリング性能を測るMetalのフレームレートメトリクス、メモリ上限超過による終了を捉えるメモリ例外診断、そして個々のクラッシュ診断をメトリクスの傾向と結びつけるクラッシュのcategoryが加わりました。1
  • 目玉となる機能はStateReportingフレームワークです。アプリが置かれている状態(アクティブなタブ、実験の振り分け、ビュー構成など)を報告すると、MetricKitが状態ごとにメトリクスを集計し、1つの平均値を画面ごとの内訳へと置き換えてくれます。1

ゼロから再構築

視聴する:Meet the new MetricKit(WWDC26)

MetricKitチームのエンジニアであるYonniが、1:23からiOS 27の再構築について紹介します。

MetricKitの役割は変わっていません。パフォーマンスワークフローにおける「収集を担う部分」であり、2種類のデータを提供します。メトリクスは、あるパフォーマンス領域が全体として改善しているか悪化しているかを教えてくれます。診断は、どのコードパスが問題を引き起こしたのかを教えてくれます。1 変わったのは、そのデータの受け取り方すべてです。セッションははっきりとこう述べています。iOS 27では、フレームワークは「コンテキストが豊かで表現力のあるモダンなSwiftファーストのAPIとしてゼロから再構築」されており、「本日お話しするすべての進化は、この新しいAPI群でのみ利用できます」。1

新しい入口はMetricManagerクラスです。デリゲートを登録してペイロードをパースする代わりに、metricReportsプロパティを非同期ストリームとしてawaitし、レポートを受け取ります。運用上のルールが2つ、セッションからそのまま示されています。「サブスクリプションの遅延によるデータ損失を避けるため」セットアップはアプリ起動時に行うこと、そして「後続のデータが準備でき次第ストリームがレポートを配信し続けられるよう」MetricManagerを生かし続けることです。1 Appleは、アプリ起動と同時にこの処理をdetachedタスクや専用のサービスクラスで実行することを推奨しています。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を通じて即座にアプリへ配信」します。1 多くの診断には、そのイベント発生時点の正確なコールスタックを示すバックトレースが含まれます。セッションの解説では、シンボリケートされたバックトレースはシステムコード内のスレッド開始から始まり、アプリへと入り、アプリのsubmitReport()関数で止まります。これが失敗の地点であり、修正の対象とすべき場所を示しています。1

クラッシュ診断は、バックトレース、終了理由、そして例外タイプを持ちます。iOS 27で新たに加わった終了のcategoryは「各クラッシュがメトリクス上でどのように扱われたかを示す」ため、「異常終了が増加傾向にある場合、それを個々の診断と直接相関させられます」。1 ダッシュボード上のメトリクスの線と、その背後にある個々のクラッシュレポートが、ようやく共通のキーを持つようになったのです。

iOS 27ではメモリ例外診断も追加されました。「アプリや拡張機能がメモリ上限を超えて終了させられたとき、何が起きたのかについてより深い洞察が得られます」。1 拡張機能も明示的に対象に含まれており、これはウィジェットや拡張機能のメモリ起因の終了を遠隔でデバッグするすべての人にとって重要です。

データの受け取り方はメトリクス側を踏襲しています。MetricManagerのインスタンスでdiagnosticReportsをawaitし、これも同じくアプリ起動時にdetachedタスクやサービスクラスから行います。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秒のヒッチ時間、つまり15 ms/sのヒッチ率を報告します。しかしその数値は「アプリの利用全体にわたる平均スクロールヒッチ率であり、たとえ誰かがReportsタブとSpendingタブを行き来していてもひとまとめにされてしまう」のです。1 アプリがヒッチしていることはわかります。しかし、どこでヒッチしているのかはわかりません。

新しいStateReportingフレームワークは、この混ぜ合わせを取り除きます。状態とは「アプリの構成や挙動を記述する、あなたが定義する情報であり、MetricKitがそれらの特性の関数としてメトリクスを集計できるようにするもの」です。1 ユーザーがタブを移動するたびにアプリは各遷移を報告し、MetricKitはそれらの状態をメトリクスや診断のデータと交差させます。1

デモにおける成果こそ、この再構築を正当化する瞬間です。1つの平均された15 ms/sという数字の代わりに、メトリクスが状態ごとに届きます。Spendingタブのスクロールは1 ms/sで「驚くほどなめらか」だった一方、Reportsタブは「71 ms/sまで跳ね上がっていた」のです。1 セッションは、平均値では決して導けなかった結論を引き出します。「Spendingタブのパフォーマンスは素晴らしい! しかしReportsタブは深刻な中断を経験しており、まさにそこにこそ最適化の労力を集中すべきなのです」。1 1つの数字が、判定であり作業指示書へと変わりました。

状態は、開始と終了で囲む方式ではなく、遷移モデルに従います。「開始と終了のペアはありません。アプリはその時々で自分が置かれている状態を報告します」。そしてMetricKitは、アプリが各状態にどれだけの時間とどまったかを追跡します。1

ドメイン、メタデータ、そして状態ごとのエンコーディング

各状態は1つのドメインにスコープされます。ドメインは「アプリの機能や領域を記述する」ものです。1つのドメインは同時に1つのアクティブな状態しか保持できませんが、ドメインを分ければ複数の状態を同時に進行させられます。1 セッションの例はA/B実験です。実験的な変更を有効にすると、経費はデータベースから小さなバッチで取得され、無効にすると大きなバッチで取得されます。タブの状態とバッチサイズの状態を別々のドメインに置くことで、「MetricKitは各タブと各バッチサイズについて別々のメトリクスを配信」します。1 画面ごとのテレメトリと実験の読み取り結果が、同じパイプラインから、しかもフィールドで得られるのです。

導入はセッションでは3つのステップに分かれています。まずStateReportingフレームワークをインポートし、ドメインを作成して(「通常は逆引きDNS形式の文字列」)、MetricManagerのインスタンスをセットアップする際にそれを登録します。次に、アプリが各状態に入るタイミングで遷移を報告します。たとえば「Reports」という文字列で識別される状態への遷移、といった具合です。1 より細かい粒度が必要な場合は、ReportableMetadataマクロを使って独自の構造体を定義し、そのメタデータ型を持つStateReporterを作成して、ラベルと独自の型の両方を添えて遷移を報告します。セッションのViewConfigurationの例は、listSizeの値とリストがソートされているかどうかを持っています。1 繰り返しになりますが、セッションはこのフローを完全なシグネチャを伴わずにスライドで示しているため、その形は写すべき構文ではなく、ドキュメントで確認すべきものとして扱ってください。

受け取る側では、レポートに2つ目の軸が加わります。状態が何も報告される前は、メトリクスレポートのstateEntriesプロパティは空です。導入後、レポートはStateEntryの値を持つようになり、それぞれが「その個々の状態に費やされた時間にわたって集計されたメトリクス値」を保持します。1 サーバーパイプライン向けには、エンコードされた出力をドメインごとにグループ化できます。JSONEncoderuserInfoプロパティにencodingFormatKeyキーをbyStateReportingDomainに設定すると、エンコードされたレポートは状態エントリとインターバルエントリの両方を「レポート内に存在する各ドメインと状態ごとにグループ化して」提示します。1

ベストプラクティス、そしてどこから始めるか

セッションは、苦労して得たスキーマ設計の助言のように読める指針で締めくくられます。ドメインは1つのアプリ領域に狭くスコープすべきです。状態遷移は「一時的なUIイベントではなく、安定した意味のあるフェーズを表すべき」です。1 退行が現れたとき、その状態だけで修正対象を絞り込めるだけの情報が得られるよう、各状態を設計してください。そして、何でも計測したくなる衝動は抑えましょう。「状態が多すぎると、データが細かくなりすぎて、かえって全体像を解釈しにくくなることがあります」。状態数には上限があり、オーバーヘッドを最小限に抑えるためのものです(セッションでは具体的な数値は示されていません)。1 出荷前には、Points of Interestインストゥルメントを使って、報告された状態が想定どおりかを検証してください。1

収集側はシステムの半分にすぎません。セッションは「すべてのデバイスにまたがってメトリクスを分析するのはデータサイエンスの問題です」と率直に述べています。レポートを取り込むサーバーを立ち上げ、関心のある次元に沿って集計し、ベースラインを確立し、どちらの方向への変化も監視するのです。1 CodableなレポートとbyStateReportingDomainエンコーディングは、まさにそのパイプラインを支えるために存在します。

既存の採用者に向けて、最後の指示は明確です。「もしMXMetricManager APIを使っているなら、これらすべての新機能を活用するために新しいMetricManager APIへ移行してください」。1 新しいAPIこそ、セッションで紹介されたあらゆる進化が宿る場所であり、セッションはそれを「フレームワークの未来」と位置づけています。1

FAQ

iOS 27でMetricKitは実際に何が変わったのですか?

フレームワークはモダンなSwiftファーストのAPIとして再構築されました。入口は新しいMetricManagerクラスです。メトリクスと診断のレポートはawait可能な非同期ストリーム(metricReportsdiagnosticReports)として届きます。レポートは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は、今年の二部構成の物語におけるフィールド側の半分です。Instrumentsはラボの中でヒッチを見せてくれ、状態を意識したMetricKitは実際のユーザーにとってどの画面がヒッチするのかを教えてくれます。ラボ側の話はInstruments 27とアプリの応答性で扱っています。71 ms/sのタブを実際に直すレンダリングの作業はiOS 27におけるSwiftUIのパフォーマンスと相互運用にあります。そして、そもそも平均値がなぜ誤解を招くのかはパフォーマンスの死角のテーマです。シリーズ全体のハブはApple Ecosystemシリーズです。

参考文献


  1. Apple, WWDC 2026 session 222, Meet the new MetricKit. iOS 27のゼロからの再構築とSwiftファーストAPIという位置づけ、MetricManagerという入口とmetricReports / diagnosticReportsの非同期ストリーム、CodableなレポートとJSONEncoderの使用、intervalEntriesとメトリクスグループ(.cpu.memory.display.gpupeakMemory)、Metalのフレームレートメトリクス、メモリ例外診断、クラッシュ終了のcategorysubmitReport()のバックトレース解説、StateReportingフレームワーク(ドメイン、遷移モデル、StateReporterReportableMetadatastateEntriesencodingFormatKey経由のbyStateReportingDomain)、経費アプリのデモの数値(平均15 ms/s、Spendingタブ1 ms/s対Reportsタブ71 ms/s)、状態のベストプラクティスとPoints of Interestによる検証、そしてMXMetricManagerからMetricManagerへ移行するという指針の出典。 

関連記事

Instruments 27でアプリの応答性はどう進化したか

Instruments 27はTop Functions、Run Comparisons、Swift executorsインストゥルメント、新しいInspectorパネルを追加し、CPU・アクター・syscallに起因するハングを診断できま…

1 分で読める

iOS 27におけるSwiftUIのパフォーマンスと相互運用

iOS 27のSwiftUIがlazy stackのスクロール、GPUシェーダーエフェクト、AppKit/UIKitとの相互運用をどう扱うかを、WWDC26のUI Frameworks公式セッション3本から読み解きます。

2 分で読める

76から100へ:Lighthouseパーフェクトスコア達成への道

個人ポートフォリオサイトのモバイルLighthouseパフォーマンススコアを76(CLS 0.493)から全カテゴリ100/100/100/100のパーフェクトスコアに改善した方法を解説します。

3 分で読める