Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
MetricKitで予期せぬ終了を検知する話 / Detect unexpected term...
Search
nekowen
April 18, 2024
Programming
1
1k
MetricKitで予期せぬ終了を検知する話 / Detect unexpected termination with MetricKit
Ebisu.mobile #5〜モバイルアプリの品質改善どうしてる?〜
https://hey.connpass.com/event/313395/
nekowen
April 18, 2024
Tweet
Share
More Decks by nekowen
See All by nekowen
アプリ起動時間を80%高速化した話 / Talk about 80% faster app startup time.
nekowen
0
170
健康第一!MetricKitで始めるアプリの健康診断 / App Health Checkups Starting with MetricKit
nekowen
5
6k
5分で理解するAccessorySetupKit / Understanding AccessorySetupKit in 5 minutes
nekowen
0
420
SwiftUI/Jetpack Composeを採用してよかったこと悪かったこと
nekowen
2
1.5k
iOS13向けに位置情報周りを対応しようとしたら苦労した話
nekowen
1
500
Other Decks in Programming
See All in Programming
デザイナーが Androidエンジニアに 挑戦してみた
874wokiite
0
270
知っているようで知らない"rails new"の世界 / The World of "rails new" You Think You Know but Don't
luccafort
PRO
1
100
JSONataを使ってみよう Step Functionsが楽しくなる実践テクニック #devio2025
dafujii
1
510
Swift Updates - Learn Languages 2025
koher
2
470
ユーザーも開発者も悩ませない TV アプリ開発 ~Compose の内部実装から学ぶフォーカス制御~
taked137
0
140
2025 年のコーディングエージェントの現在地とエンジニアの仕事の変化について
azukiazusa1
21
11k
複雑なドメインに挑む.pdf
yukisakai1225
5
1.1k
AWS発のAIエディタKiroを使ってみた
iriikeita
1
180
[FEConf 2025] 모노레포 절망편, 14개 레포로 부활하기까지 걸린 1년
mmmaxkim
0
1.6k
Android 16 × Jetpack Composeで縦書きテキストエディタを作ろう / Vertical Text Editor with Compose on Android 16
cc4966
0
160
アプリの "かわいい" を支えるアニメーションツールRiveについて
uetyo
0
210
Navigation 2 を 3 に移行する(予定)ためにやったこと
yokomii
0
100
Featured
See All Featured
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
358
30k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
46
7.6k
The Cult of Friendly URLs
andyhume
79
6.6k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.9k
What's in a price? How to price your products and services
michaelherold
246
12k
The Art of Programming - Codeland 2020
erikaheidi
55
13k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
229
22k
Making Projects Easy
brettharned
117
6.4k
Writing Fast Ruby
sferik
628
62k
Speed Design
sergeychernyshev
32
1.1k
How to Ace a Technical Interview
jacobian
279
23k
Transcript
STORES 株式会社 @nekowen Ebisu.mobile #5〜モバイルアプリの品質改善どうしてる?〜 2024年4月19日(金) 12:00 - 13:00 MetricKitで予期せぬ終了を
検知する話
自己紹介 Ryoya Kobitsu / @nekowen STORESレジ・予約開発グループ WWDC24に参加します(ドキドキ) X: @n3k0w3n GitHub:
@nekowen 2
3 ある日の出来事…
ある日の出来事… 4 QAチームから 「1日に数回アプリが突然クラッシュする」 という報告をもらう
5 何が起きていたか
何が起きていたか 6 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった
何が起きていたか 7 - watchdogに引っかかった - 無効なコード署名 - CPUへ負荷をかけオーバー ヒートした -
大量にメモリを使用して Jetsamイベントを引き起こ した 引用 https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-d iagnostic-logs#Locate-crash-reports-and-memory-logs-on-the-device
何が起きていたか 8 - watchdogに引っかかった - 無効なコード署名 - CPUへ負荷をかけオーバー ヒートした -
大量にメモリを使用して Jetsamイベントを引き起こ した 引用 https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-d iagnostic-logs#Locate-crash-reports-and-memory-logs-on-the-device
何が起きていたか 9 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった
何が起きていたか 10 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった🐛
何が起きていたか 11 - Crashlytics, TestFlightにレポートが上がっていなかった🤔 - システムによって強制終了された可能性 - 問題が起きた端末でJetsamイベントが記録されていた -
メモリの使いすぎでアプリが強制終了されていた - → 特定の条件でメモリを消費し続けるバグが見つかった 何らかの方法で検知できないか?🤔
12 MetricKitとは何か
MetricKitとは何か 13 - システムがキャプチャしたアプリの メトリクスや診断レポートをデバイ ス上で受け取れるフレームワーク - Xcode Organizerより詳細なレポー トが得られる
- iOS13から提供されている 引用 https://developer.apple.com/videos/play/wwdc 2020/10081
MetricKitとは何か 14 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性 ディスクアクセス メトリクス
診断データ
MetricKitとは何か - 全体で得られる情報 15 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ アプリの起動時間 CPU使用率 メモリ使用量 位置情報取得に使った時間 ネットワーク通信量etc… クラッシュ全般 アプリの起動時間 アプリのハングetc…
MetricKitとは何か - 配信されるタイミング 16 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ iOS15 / macOS12以降の場合 レポートがあれば即時配信される 1日に最大1回アプリに対し てレポートが配信される
MetricKitとは何か - メトリクス/パフォーマンスで得られる情報 17 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ アプリ起動にかかった時間 アクティブ時間 アプリ終了回数・メモリ使用量
MetricKitとは何か - メトリクス/パフォーマンスで得られる情報 18 エネルギー効率 パフォーマンス 応答性 ディスクアクセス クラッシュレポート 応答性
ディスクアクセス メトリクス 診断データ アプリ起動にかかった時間 アクティブ時間 アプリ終了回数・メモリ使用量 回数の変化を追っていけば 異常が起きていることがわかりそう👀
19 MXAppExitMetricから アプリの終了回数を知る
MXAppExitMetricからアプリの終了回数を知る 20 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount
MXAppExitMetricからアプリの終了回数を知る 21 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount 正常・異常終了の累積数
MXAppExitMetricからアプリの終了回数を知る 22 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount システムによって 強制終了された累積数
MXAppExitMetricからアプリの終了回数を知る 23 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount クラッシュの累積数
MXAppExitMetricからアプリの終了回数を知る 24 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount BackgroundTaskのタイムアウトによって 終了させられた累積数
MXAppExitMetricからアプリの終了回数を知る 25 - cumulativeNormalAppExitCount - cumulativeAbnormalExitCount - cumulativeAppWatchdogExitCount - cumulativeCPUResourceLimitExitCount
- cumulativeMemoryResourceLimitExitCount - cumulativeMemoryPressureExitCount - cumulativeSuspendedWithLockedFileExitCount - cumulativeBadAccessExitCount - cumulativeIllegalInstructionExitCount - cumulativeBackgroundTaskAssertionTimeoutExitCount メモリに関するシステム終了の 累積カウント ※分析したい数値
26 実装してみよう
実装してみよう 27 class AppMetrics: NSObject { let shared = MXMetricManager.shared
func startReceiveReport() { shared.add(self) } func pauseReceiveReport() { shared.remove(self) } }
実装してみよう 28 class AppMetrics: NSObject { let shared = MXMetricManager.shared
func startReceiveReport() { shared.add(self) } func pauseReceiveReport() { shared.remove(self) } } MXMetricManagerの共有インスタンスに 通知先のオブジェクトをaddする
実装してみよう 29 class AppMetrics: NSObject { let shared = MXMetricManager.shared
func startReceiveReport() { shared.add(self) } func pauseReceiveReport() { shared.remove(self) } } 通知を止めたいときは removeメソッドを呼ぶ
実装してみよう 30 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたい場合 sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } }
実装してみよう 31 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたい場合 sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } }
実装してみよう 32 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたいとき sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } } メトリクスを受け取るときは func didReceive([MXMetricPayload]) を定義する
実装してみよう 33 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたいとき sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } } 診断データを受け取るときは func didReceive([MXDiagnosticPayload]) を定義する
実装してみよう 34 extension AppMetrics: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload])
{ payloads.forEach { if let applicationExitMetrics = $0.applicationExitMetrics { // バックグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryResourceLimitExitCount) logger.info(applicationExitMetrics.backgroundExitData.cumulativeMemoryPressureExitCount) // フォアグラウンドでアプリが終了したとき logger.info(applicationExitMetrics.foregroundExitData.cumulativeMemoryResourceLimitExitCount) } // まるっとPayloadをサーバーに送りたいとき sendPayloadToServer($0.jsonRepresentation()) } } func didReceive(_ payloads: [MXDiagnosticPayload]) { payloads.forEach { sendPayloadToServer($0.jsonRepresentation()) } } } PayloadをJSONに変換するメソッドが用意されて いるので、まるっとサーバーに送る場合は使う
まとめ 35 - MetricKitを使うことでアプリの健康状態をレポートベースで 受け取れて独自に分析ができます - MXAppExitMetricを見ることで検知しづらいシステムによる 強制終了の詳細を数値で追える - 改善やっていきましょう!
36 ご清聴ありがとうございました