Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
MetricKitで予期せぬ終了を検知する話 / Detect unexpected term...
Search
nekowen
April 18, 2024
Programming
1.4k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
MetricKitで予期せぬ終了を検知する話 / Detect unexpected termination with MetricKit
Ebisu.mobile #5〜モバイルアプリの品質改善どうしてる?〜
https://hey.connpass.com/event/313395/
nekowen
April 18, 2024
More Decks by nekowen
See All by nekowen
AccessorySetupKitで実現するシームレスなペアリング体験 / Seamless pairing with AccessorySetupKit
nekowen
0
1.1k
アプリ起動時間を80%高速化した話 / Talk about 80% faster app startup time.
nekowen
0
290
健康第一!MetricKitで始めるアプリの健康診断 / App Health Checkups Starting with MetricKit
nekowen
5
7.1k
5分で理解するAccessorySetupKit / Understanding AccessorySetupKit in 5 minutes
nekowen
0
780
SwiftUI/Jetpack Composeを採用してよかったこと悪かったこと
nekowen
2
1.6k
iOS13向けに位置情報周りを対応しようとしたら苦労した話
nekowen
1
580
Other Decks in Programming
See All in Programming
Seven New Principles of Software Engineering
yegor256
0
470
AI Augmentation Techniques with Liberty
ivargrimstad
1
410
TS 7.0 で baseUrl が使えなくなった今、パスエイリアスをどうするか
bicstone
2
570
Next.js: The final boss of Web Performance
guaca
0
390
Rails初心者がパスキーを実装してみて大変だったこと
ktombow
0
160
React Native経験者が Flutterを30⽇間触って驚いたこと
3saito
0
160
自作Discord音声botをLambda MicroVMsに移行した話 / Migrating My Discord Voice Bot to Lambda MicroVMs
akihisaikeda
1
300
複数ドメインを一つのアプリに統合するFlutterの設計と運用_FlutterKaigi2026登壇資料(須永高弘)
takahirosunaga
0
160
느린 네트워크와 저사양 디바이스 사용자를 위한 웹뷰 로딩 시간 개선기
kmkim
1
220
Kawaiicon: Kubernetes, it's no longer cool, but let's secure it anyway!
barnbarn
0
130
Spec-Driven Development - Why Vaadin Is the Perfect Fit
simas
PRO
0
240
Web3Dライブラリを作ってVue Custom Rendererの仕組みを理解しよう / lets learn vue custom renderer by making 3d library
drumath2237
1
130
Featured
See All Featured
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
880
Faster Mobile Websites
deanohume
310
32k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
350
Navigating Team Friction
lara
192
16k
The Language of Interfaces
destraynor
162
27k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
brightonSEO & MeasureFest 2025 - Winning Strategies for Black Friday CRO & PPC - Christian Goodrich
cargoodrich
3
890
Why You Should Never Use an ORM
jnunemaker
PRO
61
10k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
400
The World Runs on Bad Software
bkeepers
PRO
72
12k
WENDY [Excerpt]
tessaabrams
14
40k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
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 ご清聴ありがとうございました