WWDC Quick Look 💓 By SwiftGGTeam
Profile and optimize power usage in your app

Profile and optimize power usage in your app

元の動画を見る

ハイライト

Power Profiler は Xcode から切り離され、デバイス上で直接電力消費の trace を採取できるようになりました。通勤中、CarPlay、屋外でしか発生しないような電力問題までカバーできます。


主要内容

多くの app の電力消費 bug には共通した症状があります。エンジニアの手元では問題なく動くのに、ユーザーの手に渡るとバッテリーを食い尽くしてしまうというものです。Wiam はセッション内で自身の例を挙げています。Destination Video app の同僚から「この app はバッテリー消費ランキングの首位に居座り続けている」と苦情を受けていたものの、彼女自身がいくら試しても再現できなかったといいます。理由はシンプルで、同僚は通勤中に使っていて位置情報が常に変化しているのに対し、彼女はデスクで座っているため位置がほとんど変わらず、電力を消費するコードパスがそもそも実行されていなかったのです。

WWDC25 での解決策は、Power Profiler を Xcode からデバイス本体に持ち込むというものでした。開発者は iPhone 上で直接 Performance Trace を起動でき、QA や beta ユーザーに数時間使ってもらった後、その trace ファイルを Mac に戻して Instruments で開けます。このフローはケーブル接続も Xcode の常駐も不要で、これまでもっとも再現が難しかったいくつかのシナリオ — CarPlay ナビ、AR の屋外利用、長時間のバックグラウンド電力消費 — をカバーできるようになりました。これに加えて従来からあるデスクトップ側の Power Profiler テンプレート(システムレベルの電力消費、CPU / GPU / Display / Network の 4 サブシステムごとの power impact、そして Time Profiler を含む)と組み合わせることで、開発者はデスクから屋外まで一貫した電力消費のエビデンスチェーンを初めて手に入れたことになります。


詳細

セッション全体は 4 つの具体的なシナリオを軸に展開されます。再現可能な問題、再現不可能な問題、案の比較、能動的なモニタリングです。

シナリオ 1: 再現可能な CPU スパイク(02:24。Wiam が app に Library pane を追加したところ、Xcode Organizer の energy report が即座に CPU 使用量の急増を警告しました。彼女は Instruments で blank テンプレートを選び、Power Profiler と CPU Profiler を追加した上で、ワイヤレス接続のデバイスを Profile します。すると CPU power impact のレーンで、Library pane を開く前の平均値が 1 だったものが、開いた瞬間に 21 まで跳ね上がりました(05:20)。Time Profiler の Heaviest Stack Trace は VideoCardView を直接指し示しており、LibraryThumbnailView が VStack で何千もの動画カードを一度に作っていたことが原因でした。

修正方法は、VStack を LazyVStack に置き換えるだけです(07:30)。

// Before: すべての VideoCardView を一度に作成
struct LibraryThumbnailView: View {
    let videos: [Video]

    var body: some View {
        ScrollView {
            VStack {
                ForEach(videos) { video in
                    VideoCardView(video: video)
                }
            }
        }
    }
}

// After: 表示領域内の view のみレンダリング
struct LibraryThumbnailView: View {
    let videos: [Video]

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(videos) { video in
                    VideoCardView(video: video)
                }
            }
        }
    }
}

ポイント:

  • VStack は body の評価時点ですべての子 view を即座に生成して保持するため、リストが長くなるほど起動コストとメモリ使用量が膨らみます。
  • LazyVStack は現在表示されている、もしくはこれからスクロールで viewport に入る item に対してのみ view を生成し、外に出たら破棄します。
  • データソース videos は変わらず、View ツリーの構造も変わらず、変更したのはコンテナの種類だけ。これがまさに SwiftUI が Lazy 系コンテナを提供している理由です。
  • 修正後に同じ区間を再度 Profile したところ、CPU power impact の平均値は 21 から 4.3 まで下がりました(08:32)。

シナリオ 2: デバイス上での採取(09:27。有効化までの手順は次の通りです。Settings -> Developer -> Performance Trace -> Power Profiler のスイッチを ON -> 対象の app を選択(Xcode、TestFlight、エンタープライズ証明書経由でインストールした app に限定)。続いて画面右上から下にスワイプして Control Center を開き、Performance Trace アイコンを追加してタップして開始します。録画は数時間連続でも可能で、もう一度アイコンをタップすれば停止します。生成された trace ファイルを AirDrop で Mac に送り、Instruments でそのまま開けます。デバイス側モードの Time Profiler はサンプリングレートを意図的に低く抑えており、観察者効果を抑えるための設計です。

Wiam は同僚から送られてきた trace を使って、videoSuggestionsForLocation が大量の CPU 時間を消費していることを突き止めました(13:32)。この関数は位置に基づいて近くの動画をレコメンドする役割を持ち、位置が変わるたびに呼び出されます。問題のコードは、呼ばれるたびにサイズの大きな RecommendationRules JSON ファイルを読み直し、毎回 decode していました。

// 問題: 位置更新のたびにファイルを再読み込み + JSON を全量パース
func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    let url = Bundle.main.url(forResource: "RecommendationRules", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    let rules = try! JSONDecoder().decode([RecommendationRule].self, from: data)
    return filter(videos: allVideos, with: rules, near: location)
}

// 最適化: rules は実行期に不変なので、遅延読み込み + キャッシュ
private static let recommendationRules: [RecommendationRule] = {
    let url = Bundle.main.url(forResource: "RecommendationRules", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    return try! JSONDecoder().decode([RecommendationRule].self, from: data)
}()

func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    return filter(videos: allVideos, with: Self.recommendationRules, near: location)
}

ポイント:

  • 高頻度なコールバック内でのファイル I/O や JSON parse は典型的な電力消費の罠です。JSON が大きく、位置更新の頻度が高いほど影響は深刻になります。
  • rules を static let と即時実行クロージャに切り出すと、スレッドセーフな lazy loading と等価になります(Swift は let の静的プロパティの初期化が一度だけ行われることを保証します)。
  • 修正後は同じ通勤シナリオで同僚にもう一度 trace を録ってもらい、問題が解消されていること、かつ新たな電力消費のリグレッションが入っていないことを確認します(15:51)。

シナリオ 3: 案の比較(16:19。実装案 A と B があるとして、A は小規模データで CPU を節約でき、B は大規模データでネットワークを節約できる、というケースが考えられます。コードを眺めるだけでは正味の利得は判断できません。アプローチとしては、それぞれを Profile し、複数回実行して平均を取り、thermal 状態・デバイスの状態・システム負荷・app 内の機能 ON/OFF を揃えた上で横並びで比較します。

シナリオ 4: 縦深防御(18:25。Apple の公式な推奨は、電力消費ツールをパイプラインとして繋げることです。コーディング中は Xcode Energy Gauges でリアルタイムにフィードバックを見る -> 疑わしい挙動があれば Instruments の Power Profiler で深掘りする -> CI で XCTests を使って自動検出する -> リリース後は Xcode Organizer / MetricKit で実ユーザーをモニタリングする -> App Store Connect API でリモートの集計データを取得する、という流れです。


重要ポイント

  • 今すぐ Power Profiler trace を一度走らせる: デバイスを接続して、主要な利用パスを一通り Profile し、CPU / GPU の power impact に予期しないスパイクが出ていないかを確認しましょう。これはもっとも低コストな電力消費監査であり、問題が見つからないこと自体も良い結果です。
  • VStack / HStack はデフォルトで LazyVStack / LazyHStack に置き換える: データ量が明らかに 20 件未満であると分かっている場合を除き、リストやグリッドには Lazy 系コンテナを使いましょう。Wiam の事例で 21 -> 4.3 を実現した鍵となる修正であり、コストはほぼゼロです。
  • デバイス側の Performance Trace を dogfood / QA に活用する: チームメンバーに実際の通勤、屋外、CarPlay のシナリオで trace を採取してもらい、回収しましょう。このフローを beta テストのドキュメントに組み込めば、デスクでは絶対に再現できないような電力消費 bug を掘り出せます。
  • 位置情報・timer・通知などの高頻度コールバック内でファイル I/O や JSON parse を行わない: 不変なデータは static let のクロージャで lazy にキャッシュするのが、この種の問題を直接的に解消するもっとも素直なパターンです。
  • パフォーマンス修正のたびに比較用の trace を録る: 修正の効果を確認すると同時に、新たなリグレッションが入っていないことも確認します。Power Profiler の比較方法論では thermal やデバイス状態などの変数を制御し、複数回実行した平均を見るのが基本です。

関連 Session

コメント

GitHub Issues · utterances