ハイライト
iOS 27ではMetricKitがSwiftで全面的に書き直され、新しい
MetricManagerとStateReportingフレームワークが導入されます。開発者は、現在のタブやユーザーフローといったアプリの状態ごとにパフォーマンス指標をクロス分析でき、さらにメモリ例外診断とMetalフレームレート指標も利用できます。
重要ポイント
アプリを公開したあと、ユーザーから「少し重い」と言われたとします。Xcode Instrumentsを開くと、すべて正常に見えます。では問題はどこにあるのでしょうか。実機上のパフォーマンス問題は、シミュレータ上の挙動とは大きく違います。ユーザーは古い端末を使っているかもしれませんし、バッテリー残量が少ない状態や、通信環境が悪い場所で使っているかもしれません。必要なのは、現実の利用環境から得られるデータです。
MetricKitはそのための仕組みです。アプリが実際のデバイス上で記録したパフォーマンスデータを、毎日レポートとしてまとめてアプリに届けます。そのデータを自分のサーバーにアップロードし、集計して問題を見つけられます。
ただし、従来のMetricKitには大きな弱点がありました。すべての指標がグローバル平均だったことです。アプリに3つのタブがあり、MetricKitが「1日の平均スクロールひっかかり率は15ms/s」と教えてくれたとします。この数値自体は正しくても、どのタブで起きたのかは分かりません。Spendingタブは非常に滑らかで、Reportsタブだけが使いものにならない可能性があります。グローバル平均は問題を埋もれさせます。
iOS 27の解決策は2つです。Swiftファーストの新しいAPIと、StateReportingというフレームワークです。
新APIでは古いdelegateコールバックを捨て、async forループを使います。MetricManagerはmetricReportsとdiagnosticReportsという2つのAsyncSequenceを提供し、コードはかなりすっきりします。レポート自体もCodableになったので、JSONEncoderでそのままエンコードしてサーバーへ送れます。手書きの解析ロジックは不要です。
StateReportingが解決するのは、「指標をどの軸で分けるべきか」という問題です。現在どのタブにいるか、どのユーザーフローにいるかなど、アプリの重要な状態を定義すると、MetricKitはその状態ごとに指標を集計します。先ほどの例なら、StateReportingを追加したあとに、Spendingタブのひっかかり率は1ms/sしかない一方で、Reportsタブは71ms/sに達していると分かります。最適化すべき場所がすぐ明確になります。
詳細
新しいMetricManager API
(00:04:58)
iOS 27のMetricKitは純粋なSwiftで書き直されています。入口はMXMetricManagerからMetricManagerに変わります。
import MetricKit
let manager = MetricManager()
for await report in manager.metricReports {
processReport(report)
}
ポイント:
MetricManager()は新しい入口クラスで、従来のMXMetricManagerを置き換えますmetricReportsはAsyncSequenceで、for awaitを使って日次レポートを受け取ります- このコードはアプリ起動時にできるだけ早く実行する必要があります。購読が遅れると、その日のデータを取り逃がす可能性があります
- 以後のレポートを継続して受け取るため、
MetricManagerは生存させておく必要があります
(00:05:25)
レポートはCodableなので、サーバーへ送るコードは数行で済みます。
import MetricKit
for await report in manager.metricReports {
let jsonData = try JSONEncoder().encode(report)
sendToServer(jsonData)
}
ポイント:
MetricReportはCodableに準拠しています- レポート全体を
JSONEncoderで直接エンコードでき、手書きの解析モデルは不要です sendToServerは自分のネットワーク層のメソッドです
(00:05:44)
特定の指標をローカルで分析したい場合は、レポート内の区間エントリを走査します。
import MetricKit
for await report in manager.metricReports {
let intervalEntries = report.intervalEntries
let fullDayEntry = intervalEntries.fullDayEntry
for entry in intervalEntries {
let memoryMetrics = entry.values.filter { $0.metricGroup == .memory }
for metric in memoryMetrics {
switch metric {
case .peakMemory(let peak):
processPeakMemory(peak)
default: break
}
}
}
}
ポイント:
intervalEntriesには、1日全体の集計エントリと、複数の時間単位の細分化エントリが含まれますfullDayEntryは1日全体の集計データです- 各エントリの
valuesは、.memory、.cpu、.display、.gpuなどのmetricGroupで分類されます .peakMemoryのような具体的な指標型はswitchでマッチします- この処理はdetached Taskや専用のサービスクラスに置くべきです
診断レポート: 指標から根本原因へ
(00:08:59)
指標は「問題がある」と教えてくれます。診断は「どこで起きたか」を教えてくれます。
import MetricKit
let manager = MetricManager()
for await report in manager.diagnosticReports {
processReport(report)
}
ポイント:
diagnosticReportsもAsyncSequenceです- 診断レポートは日次集計を待たず、イベント発生時にすぐ配信されます
- こちらもアプリ起動時にできるだけ早く監視を始める必要があります
(00:09:14)
診断レポートもCodableに対応しています。
import MetricKit
for await report in manager.diagnosticReports {
let jsonData = try JSONEncoder().encode(report)
sendToServer(jsonData)
}
(00:09:39)
診断内容には構造化してアクセスできます。
import MetricKit
for await report in manager.diagnosticReports {
switch report.result {
case .crash(let crash):
let backtrace = crash.callStackTree
let reason = crash.terminationReason
let category = crash.terminationCategory
processCrash(backtrace: backtrace, reason: reason, category: category)
case .hang(let hang):
processHangDiagnostic(hang)
default: break
}
}
ポイント:
report.resultは.crash、.hang、.memoryExceptionなどの診断種別をenumで表しますcrash.callStackTreeはシンボル化されたスタックで、問題のコードを直接示しますterminationReasonはクラッシュ理由を説明しますterminationCategoryはiOS 27で追加されたフィールドで、クラッシュが指標上どのカテゴリに分類されるかを示し、診断と指標を関連付けやすくします
StateReporting: アプリ状態ごとに指標を分ける
(00:13:57)
これはiOS 27のMetricKitで最も重要な新機能です。状態を定義すると、MetricKitがその状態ごとに指標を集計します。
import MetricKit
import StateReporting
let domain = StateReportingDomain("com.metrickitsample.tabs")
let manager = MetricManager(enabledStateReportingDomains: [domain])
let reporter = StateReporter.reporter(for: domain.rawValue)
reporter.reportTransition(to: "Reports")
ポイント:
StateReportingDomainは状態ドメインを定義します。通常は逆DNS形式を使いますMetricManagerを作るときに、enabledStateReportingDomainsへドメインを渡しますStateReporter.reporter(for:)で、そのドメインのレポーターを取得しますreportTransition(to:)は現在の状態へ入ったことを報告します。「終了」はなく、「どの状態へ切り替わったか」だけを扱います
(00:14:21)
@ReportableMetadataマクロで構造化メタデータを付けられます。
import StateReporting
@ReportableMetadata
struct ViewConfiguration {
let listSize: String
let isSorted: Bool
}
let reporter = StateReporter.reporter(
for: domain.rawValue,
stableMetadata: ViewConfiguration.self
)
reporter.reportTransition(
to: "Reports",
stableMetadata: ViewConfiguration(listSize: "large", isSorted: false)
)
ポイント:
@ReportableMetadataマクロはカスタム構造体をマークし、コンパイラがシリアライズコードを生成しますstableMetadataパラメータでメタデータ型を指定します- 状態を報告するとき、具体的なメタデータインスタンスも渡します
- これによりMetricKitは、「Reportsタブの大きな未ソートリスト」と「Reportsタブの小さなソート済みリスト」のパフォーマンス差を区別できます
(00:15:29)
状態ごとにグループ化してエンコードします。
import MetricKit
for await report in manager.metricReports {
let encoder = JSONEncoder()
let formatKey = MetricReport.encodingFormatKey
encoder.userInfo[formatKey] = MetricReport.EncodingFormat.byStateReportingDomain
let jsonData = try encoder.encode(report)
sendToServer(jsonData)
}
ポイント:
MetricReport.encodingFormatKeyはエンコード形式の設定キーですMetricReport.EncodingFormat.byStateReportingDomainは、状態ドメインと状態ごとにグループ化したJSONを出力します- サーバー側では、状態という軸で直接集計・分析できます
iOS 27で追加された指標と診断
(00:03:23)
Metalフレームレート指標。ゲーム開発者は、レンダリングフレームレートの分布データをMetricKitから直接取得できます。自前で統計ロジックを書く必要はありません。
(00:03:55)
メモリ例外診断。アプリや拡張機能がメモリ制限を超えてシステムに終了されたとき、MetricKitはスタックと終了理由を含む診断レポートを配信します。これは以前にはなかったものです。
実装アイデア
1. 主要ユーザーフローごとのパフォーマンス像を作る
StateReportingを使い、ホーム画面の読み込み、商品詳細、支払い確認、動画再生など、アプリの重要な流れを独立した状態として定義します。公開から1週間後にデータを見ると、どの流れでHang率やメモリピークが異常なのか一目で分かります。入口はStateReportingDomainとStateReporter.reportTransition(to:)です。
2. A/B実験でパフォーマンスデータを自動収集する
実験グループもStateReportingドメインとして扱います。実験群と対照群のパフォーマンス指標が自動で分かれ、実験コードに手動でパフォーマンス計測点を埋め込む必要がありません。実験結果はビジネス指標だけでなく、パフォーマンス指標も合わせて評価できます。
3. リアルタイム診断アラートを作る
診断レポートは日次集計ではなく即時配信されます。サーバーがCrashやMemory Exceptionの診断を受け取ったら、すぐアラートを出せます。terminationCategoryフィールドを組み合わせると、「緊急修正が必要なクラッシュ」と「既知のシステムレベル終了」を区別できます。
4. 複雑なリスト画面を設定単位で監視する
@ReportableMetadataを使い、データ量、ソート有無、画像の遅延読み込み有無など、リスト画面の設定をメタデータとして報告します。「大きなリスト+未ソート」の組み合わせでスクロールひっかかり率が急増していると分かれば、最適化方針はすぐ明確になります。
5. 旧MXMetricManagerから移行する
すでにMXMetricManagerを使っている場合、移行は分かりやすいです。delegateコールバックをfor awaitループへ置き換え、手書きのJSON解析をJSONEncoder().encode(report)へ置き換えます。新APIがフレームワークの今後の中心であり、新機能はすべて新APIだけで提供されます。
関連セッション
- Metalゲームのパフォーマンス問題を見つけて修正する - Metalのフレームレート指標の具体的な利用場面と最適化方法です
- Swiftの新機能 - MetricKitの新しいAPIでは、SwiftのAsyncSequenceとマクロシステムが多く使われています
- SwiftUIの新機能 - SwiftUIアプリの画面状態は、StateReportingによるパフォーマンス分解に自然に適しています
- Instrumentsの新機能 - Points of Interest Instrumentを使って、StateReportingで報告した状態が期待どおりか確認します
- MetricKitでアプリのパフォーマンスを分析する - より深いパフォーマンス分析方法と指標の読み解き方です
コメント
GitHub Issues · utterances