Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アプリの「もっさり」を解決!MetricKitを活用したアプリのパフォーマンス改善

Avatar for otouto otouto
October 07, 2025

 アプリの「もっさり」を解決!MetricKitを活用したアプリのパフォーマンス改善

iOSDC Japan 2025にて登壇した内容になります。
----------------------------------------------------------------

―「最近アプリがもっさりしてきた」「なんだか動作がもたつく」―
こうしたパフォーマンスに関するフィードバックを一度は受け取ったことがあるのではないでしょうか。
しかし、この「もっさり」は一体なにが原因で、どれくらい遅いのか?定性的な印象だけでは、改善の手がかりがつかめません。
弊社が開発しているアプリでも、特に利用頻度の高いユーザーから「もっさりする」といった声が寄せられていました。
そんな見えない「もっさり」の正体を明らかにし、改善につなげるためにMetricKitを活用しました。

## このトークで話すこと
本トークでは、ユーザーの「アプリがもっさりする」という定性的な問題を、MetricKitを使って定量的に分析し、改善した方法を紹介します。

- MetricKitの概要と選定理由
- MetricKitを使ったパフォーマンスの見える化
- メトリクスの収集プロセス
- メトリクスの分析手法と整理方法
- メトリクスの分析から見えたボトルネックとなっていたCoreDataの処理
- 実際に行った改善アプローチとその効果
- CoreDataの処理の最適化
- 計算量を意識した実装
- 結果:処理時間を大幅短縮し、ユーザー満足度も向上
- コードのパフォーマンスを測定する方法

あなたのアプリにも、ユーザーがまだ言葉にしていない「もっさり」が潜んでいるかもしれません。
パフォーマンス改善の第一歩は、現在の状態を正しく測ること。つまり、「定量化」です。
本トークを通じて、ユーザーの体感を数字で捉え、改善へつなげる方法を知ることができます。
ユーザーから「アプリが軽くなった!」という喜びの声を聞く方法を一緒に学びましょう!

Avatar for otouto

otouto

October 07, 2025
Tweet

Other Decks in Programming

Transcript

  1. 事業領域 直販Eコマース DX/運⽤等 業務⽀援 ソリューション BtoB BtoC 定額制サービス アラカルト インディーズ

    活動⽀援 メディア ⾃主盤CD販売/グッズ販売/ライブ⽀援等 エージェント DIY向け ディストリビューション ディストリビューション ⾳楽配信 for Biz
  2. 利⽤シーン別の⽐較表 29 Xcode Organizer MetricKit データの 収集範囲 全ユーザーの 集計値 ユーザーごと

    の個別データ いつ使うか 全体の傾向を 確認 特定問題の 詳細調査
  3. "averageSuspendedMemory" : { "averageValue" : "100,000 kB", "standardDeviation" : 0,

    "sampleCount" : 500 収集されるメトリクスの例(メモリ) 37 サスペンド状態のメモリ使⽤量
  4. 収集されるメトリクスの例(ハング時間) 38 "applicationResponsivenessMetrics": { "histogrammedAppHangTime": { "histogramNumBuckets": 2, "histogramValue": {

    "0": { "bucketCount": 50, "bucketStart": "0 ms", "bucketEnd": "100 ms" }, "1": { "bucketCount": 60, "bucketStart": "100 ms", "bucketEnd": "400 ms" } n=110
  5. 問題となっていた処理 59 // メインスレッドで実⾏ apiTracks.forEach { apiTrack in let storedTrack

    = fetch(id: apiTrack.id) storedTrack.update(apiTrack) save() } APIから取得した楽曲リスト
  6. 問題となっていた処理 60 // メインスレッドで実⾏ apiTracks.forEach { apiTrack in let storedTrack

    = fetch(id: apiTrack.id) storedTrack.update(apiTrack) save() } CoreDataから楽曲情報を取得
  7. // メインスレッドで実⾏ apiTracks.forEach { apiTrack in let storedTrack = fetch(id:

    apiTrack.id) storedTrack.update(apiTrack) save() } 問題となっていた処理 61 差分を更新し保存
  8. let context = CoreDataStore.shared.viewContext LocalMusicStore.updateTracks( with: tracksInfo, in: context )

    メインスレッドの処理を改善する 69 メインスレッド⽤のcontext
  9. メインスレッドの処理を改善する CoreDataStore.performBackgroundTask { context in LocalMusicStore.updateTracks( with: tracksInfo, in: context

    ) { // 保存完了を通知する処理など } } 71 バックグラウンド⽤のcontextを使⽤
  10. CoreDataのアクセス回数を減らす 76 apiTracks.forEach { apiTrack in let storedTrack = fetch(id:

    apiTrack.id) storedTrack.update(apiTrack) save() } 楽曲の数だけCoreDataへの検索・更新処理 が⾛る
  11. 計算量を削減する 83 apiTracks.forEach { apiTrack in … = storedTracks.first {

    $0.ID == apiTrack.ID } // 更新処理 } CoreDataから取得した保存済み楽曲リスト
  12. 計算量を削減する 84 apiTracks.forEach { apiTrack in … = storedTracks.first {

    $0.ID == apiTrack.ID } // 更新処理 } first(where:)のO(n)で楽曲を取得
  13. 計算量を削減する let trackDictionary = Dictionary(uniqueKeysWithValues: storedTracks.map { ($0.ID, $0) }

    ) apiTracks.forEach { apiTrack in … = trackDictionary[apiTrack.ID] // 更新処理 } 85
  14. 計算量を削減する let trackDictionary = Dictionary(uniqueKeysWithValues: storedTracks.map { ($0.ID, $0) }

    ) apiTracks.forEach { apiTrack in … = trackDictionary[apiTrack.ID] // 更新処理 } 86 楽曲IDと楽曲のDictionary型を作成する
  15. 計算量を削減する let trackDictionary = Dictionary(uniqueKeysWithValues: storedTracks.map { ($0.ID, $0) }

    ) apiTracks.forEach { apiTrack in … = trackDictionary[apiTrack.ID] // 更新処理 } 87 O(1)で楽曲を取得