mcpField
A
axiom-energy

axiom-energy

github.com/charleswiltgen開発1.2k176 回インストール今日更新
リポジトリ

axiom-energy について

axiom-energyは、iOS開発者向けの消費電力診断・最適化スキルです。Power Profilerで測定してから、主な原因となるサブシステムに応じた対策を案内します。

主な機能

  1. 01Power Profilerによる診断手順
  2. 02CPU・GPU・ネットワーク・画面の分析
  3. 03タイマーとポーリングの最適化
  4. 04遅延読み込みとキャッシュの指針
  5. 05位置情報とバックグラウンド処理の指針

活用シーン

  1. 01バッテリー消費や端末の発熱の調査
  2. 02高い消費電力の原因となるサブシステムの特定
  3. 03バックグラウンド消費電力の削減
  4. 04変更前後の電力トレースの比較

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-energy

インストール後、Claude Code で/axiom-energyを入力して読み込まれたか確認します。

詳細

エネルギー最適化

概要

エネルギーに関する問題は、バッテリーの消耗、デバイスの発熱、App Store の低評価として現れます。基本原則:最適化の前に測定します。Power Profiler を使って支配的なサブシステム(CPU/GPU/ネットワーク/位置情報/ディスプレイ)を特定し、その後、対象を絞った修正を適用します。

重要なポイント:開発者は、監査をどこから始めればよいのかわからないことがよくあります。このスキルでは、当て推量ではなく、体系的な診断を行います。

要件:iOS 26+、Xcode 26+、Instruments の Power Profiler

プロンプトの例

このスキルが答える、開発者から寄せられる実際の質問:

1. 「バッテリー設定で、いつも自分のアプリが上位に表示されます。何が電力を消費しているのか、どうすればわかりますか?」

→ このスキルでは、支配的なサブシステムを特定する Power Profiler のワークフローと、対象を絞った修正方法を解説します。

2. 「アプリを使うとスマートフォンが熱くなるとユーザーから報告されています。デバッグはどこから始めればよいですか?」

→ このスキルでは、CPU と GPU、ネットワークのどこに原因があるかを診断するための判断ツリーと、具体的なパターンを紹介します。

3. 「タイマーと位置情報の更新を使っています。これらがバッテリー消耗の原因になっていますか?」

→ このスキルでは、タイマーの許容誤差、位置情報の精度に関するトレードオフ、監査用チェックリストを解説します。

4. 「ユーザーがアプリを使っていないときも、バックグラウンドでバッテリーが消耗します。」

→ このスキルでは、バックグラウンド実行のパターン、BGTasks、EMRCA の原則を解説します。

5. 「最適化によってバッテリー駆動時間が実際に改善したか、どうすれば測定できますか?」

→ このスキルでは、Power Profiler で最適化の前後を比較するワークフローを実演します。


警告サイン — エネルギー消費が多い可能性あり

次のいずれかに当てはまる場合、エネルギー効率が悪い可能性があります。

  • バッテリー設定:バッテリーを消費するアプリの上位に、自分のアプリが常に表示される
  • デバイスの温度:通常のアプリ使用中にスマートフォンが熱くなる
  • ユーザーレビュー:「バッテリーが減る」「スマートフォンが熱くなる」「バッテリーがすぐになくなる」といった言及がある
  • Xcode のエネルギーゲージ:影響が継続的に「高」または「非常に高い」と表示される
  • バックグラウンドでの実行時間:アプリが画面に表示されていないときも、予想以上に長く実行される
  • ネットワークアクティビティ:処理をまとめず、少量のリクエストを頻繁に送信している
  • 位置情報アイコン:アプリが位置情報を必要としないはずのときに、ステータスバーに表示される
通常のエネルギー消費との違い
  • 正常:アプリの使用中にエネルギーを消費し、バックグラウンドでは最小限に抑えられる
  • 問題あり:ユーザーが操作していないときも、アプリがかなりのエネルギーを消費する

必ず最初に行うこと

コードを最適化する前に、必ず最初に Power Profiler を実行してください。

ステップ 1:電力トレースを記録する(5 分)
1. Connect iPhone wirelessly to Xcode (wireless debugging)
2. Xcode → Product → Profile (Cmd+I)
3. Select Blank template
4. Click "+" → Add "Power Profiler" instrument
5. Optional: Add "CPU Profiler" for correlation
6. Click Record
7. Use your app normally for 2-3 minutes
8. Click Stop

ワイヤレス接続にする理由:デバイスをケーブルで充電していると、電力の測定値が 0 になります。正確に測定するには、ワイヤレスデバッグを使用してください。

ステップ 2:支配的なサブシステムを特定する

Power Profiler のトラックを展開し、アプリごとの指標を確認します。

レーン意味値が高い場合に示すもの
CPU Power Impactプロセッサのアクティビティ計算処理、タイマー、解析
GPU Power Impactグラフィックスのレンダリングアニメーション、ぼかし、Metal
Display Power Impact画面の使用状況明るさ、常時表示コンテンツ
Network Power Impact無線通信のアクティビティリクエスト、ダウンロード、ポーリング

確認する点:アプリの使用中に、どのサブシステムが最も高い値を継続的に示しているかを確認します。

ステップ 3:サブシステムに応じた修正方法を選ぶ

支配的なサブシステムを特定したら、以下の判断ツリーを使います。

診断でわかること
  • CPU が支配的 → タイマー、ポーリング、JSON 解析、先行読み込みを確認します
  • GPU が支配的 → アニメーション、ぼかし効果、フレームレートを確認します
  • ネットワークが支配的 → リクエストの頻度、ポーリングとプッシュのどちらを使っているかを確認します
  • ディスプレイが支配的 → ダークモード、明るさ、画面の点灯時間を確認します
  • 位置情報(CPU に表示)→ 精度、更新頻度を確認します
最初に診断する理由
  • Power Profiler で根本原因を見つける場合:15〜20 分
  • 推測で手当たり次第に最適化を試す場合:4 時間以上かかり、原因となるサブシステムを間違えることもよくあります

エネルギーの判断ツリー

User reports energy issue?
│
├─ CPU Power Impact dominant?
│  ├─ Continuous high impact?
│  │  ├─ Timers running? → Pattern 1: Timer Efficiency
│  │  ├─ Polling data? → Pattern 2: Push vs Poll
│  │  └─ Processing in loop? → Pattern 3: Lazy Loading
│  ├─ Spikes during specific actions?
│  │  ├─ JSON parsing? → Cache parsed results
│  │  ├─ Image processing? → Move to background, cache
│  │  └─ Database queries? → Index, batch, prefetch
│  └─ High background CPU?
│     ├─ Location updates? → Pattern 4: Location Efficiency
│     ├─ BGTasks running too long? → Pattern 5: Background Execution
│     └─ Audio session active? → Stop when not playing
│
├─ Network Power Impact dominant?
│  ├─ Many small requests?
│  │  └─ Batch into fewer large requests
│  ├─ Polling pattern detected?
│  │  └─ Convert to push notifications → Pattern 2
│  ├─ Downloads in foreground?
│  │  └─ Use discretionary background URLSession
│  └─ High cellular usage?
│     └─ Defer to WiFi when possible
│
├─ GPU Power Impact dominant?
│  ├─ Continuous animations?
│  │  └─ Stop when view not visible
│  ├─ Blur effects (UIVisualEffectView)?
│  │  └─ Reduce or remove, use solid colors
│  ├─ High frame rate animations?
│  │  └─ Audit secondary frame rates → Pattern 6
│  └─ Metal rendering?
│     └─ Implement frame limiting
│
├─ Display Power Impact dominant?
│  ├─ Light backgrounds on OLED?
│  │  └─ Implement Dark Mode (up to 70% savings)
│  ├─ High brightness content?
│  │  └─ Use darker UI elements
│  └─ Screen always on?
│     └─ Allow screen to sleep when appropriate
│
└─ Location causing drain? (check CPU lane + location icon)
   ├─ Continuous updates?
   │  └─ Switch to significant-change monitoring
   ├─ High accuracy (kCLLocationAccuracyBest)?
   │  └─ Reduce to kCLLocationAccuracyHundredMeters
   └─ Background location?
      └─ Evaluate if truly needed → Pattern 4

よくあるエネルギー消費パターン(修正方法付き)

パターン 1:タイマーの効率化

問題:タイマーによって CPU がアイドル状態から復帰し、大きなエネルギーを消費します。

❌ アンチパターン — 許容誤差のないタイマー
// BAD: Timer fires exactly every 1.0 seconds
// Prevents system from batching with other timers
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    self.updateUI()
}
✅ 修正方法 — タイマーのバッチ処理に許容誤差を設定する
// GOOD: 10% tolerance allows system to batch timers
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    self.updateUI()
}
timer.tolerance = 0.1  // 10% tolerance minimum

// BETTER: Use Combine Timer with tolerance
Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .default)
    .autoconnect()
    .sink { [weak self] _ in
        self?.updateUI()
    }
    .store(in: &cancellables)
✅ 最適 — ポーリングではなくイベント駆動を使用する
// BEST: Don't use timer at all — react to events
NotificationCenter.default.publisher(for: .dataDidUpdate)
    .sink { [weak self] _ in
        self?.updateUI()
    }
    .store(in: &cancellables)

重要なポイント:

  • 許容範囲を間隔の**少なくとも 10%**に設定します
  • タイマーの許容範囲を設定すると、システムは複数のタイマーをまとめて 1 回のウェイクアップにできます
  • ポーリングタイマーよりもイベント駆動のパターンを優先します
  • 不要になったタイマーは必ず無効化します

パターン 2:プッシュとポーリングの比較

問題:ポーリング(N 秒ごとにサーバーを確認する方法)は無線をアクティブな状態に保ち、バッテリーを消耗します。

❌ アンチパターン — 5 秒ごとのポーリング
// BAD: Polls server every 5 seconds
// Radio stays active, massive battery drain
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
    self?.fetchLatestData()  // Network request every 5 seconds
}
✅ 修正 — バックグラウンドプッシュ通知を使用する
// GOOD: Server pushes when data changes
// Radio only active when there's actual new data

// 1. Register for remote notifications
UIApplication.shared.registerForRemoteNotifications()

// 2. Handle background notification
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

    guard let _ = userInfo["content-available"] else {
        completionHandler(.noData)
        return
    }

    Task {
        do {
            let hasNewData = try await fetchLatestData()
            completionHandler(hasNewData ? .newData : .noData)
        } catch {
            completionHandler(.failed)
        }
    }
}

バックグラウンドプッシュ用のサーバーペイロード:

{
    "aps": {
        "content-available": 1
    },
    "custom-data": "your-payload"
}

重要なポイント:

  • バックグラウンドプッシュは任意のタイミングで配信されるため、システムが最適なタイミングで配信します
  • 緊急ではない更新には apns-priority: 5 を使用します(エネルギー効率が高くなります)
  • 時間的制約のあるアラートに限り apns-priority: 10 を使用します
  • 5 秒ごとのポーリングは、プッシュの100 倍のエネルギーを消費します

パターン 3:遅延読み込みとキャッシュ

問題:すべてのデータを事前に読み込むと、CPU の負荷が急増し、メモリに負担がかかります。

❌ アンチパターン — 先行読み込み(WWDC25-226 より)
// BAD: Creates and renders ALL views upfront
// From WWDC25-226: This caused CPU spike and hang
VStack {
    ForEach(videos) { video in
        VideoCardView(video: video)  // Creates ALL thumbnails immediately
    }
}
✅ 修正 — 遅延読み込み
// GOOD: Only creates visible views
// From WWDC25-226: Reduced CPU power impact from 21 to 4.3
LazyVStack {
    ForEach(videos) { video in
        VideoCardView(video: video)  // Creates on-demand
    }
}
❌ アンチパターン — 繰り返しの解析(WWDC25-226 より)
// BAD: Parses JSON file on every location update
// From WWDC25-226: Caused continuous CPU drain during commute
func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    // Called every location change!
    let data = try? Data(contentsOf: rulesFileURL)
    let rules = try? JSONDecoder().decode([RecommendationRule].self, from: data)
    return filteredVideos(using: rules)
}
✅ 修正 — 解析済みデータをキャッシュする
// GOOD: Parse once, reuse cached result
// From WWDC25-226: Eliminated CPU drain
private lazy var cachedRules: [RecommendationRule] = {
    let data = try? Data(contentsOf: rulesFileURL)
    return (try? JSONDecoder().decode([RecommendationRule].self, from: data)) ?? []
}()

func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    return filteredVideos(using: cachedRules)  // No parsing!
}

重要なポイント:

  • 大規模なコレクションには LazyVStack、LazyHStack、LazyVGrid を使用します
  • 解析済み JSON、デコード済みデータ、計算結果をキャッシュします
  • 負荷の高い処理は、頻繁に呼び出されるメソッドの外に移します

パターン 4:位置情報の効率化

問題:位置情報を継続的に更新すると GPS がアクティブな状態に保たれ、バッテリーを急速に消耗します。

❌ アンチパターン — 高精度で継続的に更新する
// BAD: Continuous updates with best accuracy
// GPS stays active constantly, massive battery drain
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()  // Never stops!
✅ 修正 — 適切な精度と大幅な変更の検出を使用する
// GOOD: Reduced accuracy, significant-change monitoring
let locationManager = CLLocationManager()

// Use appropriate accuracy (100m is fine for most apps)
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters

// Use distance filter to reduce updates
locationManager.distanceFilter = 100  // Only update every 100 meters

// For background: Use significant-change monitoring
locationManager.startMonitoringSignificantLocationChanges()

// Stop when done
func stopTracking() {
    locationManager.stopUpdatingLocation()
    locationManager.stopMonitoringSignificantLocationChanges()
}
✅ さらに効果的 — iOS 26 以降では、静止検出機能を備えた CLLocationUpdate を使用する
// BEST: Modern async API with automatic stationary detection
for try await update in CLLocationUpdate.liveUpdates() {
    if update.stationary {
        // Device stopped moving — system pauses updates automatically
        // Switch to CLMonitor for region monitoring
        break
    }
    handleLocation(update.location)
}

精度の比較(バッテリーへの影響):

精度バッテリーへの影響用途
kCLLocationAccuracyBest非常に大きいナビゲーションアプリのみ
kCLLocationAccuracyNearestTenMeters大きいフィットネストラッキング
kCLLocationAccuracyHundredMeters中程度店舗検索
kCLLocationAccuracyKilometer小さい天気アプリ
大幅な変更の検出非常に小さいバックグラウンド更新

パターン 5:バックグラウンド実行(EMRCA)

問題:実行時間が長すぎる、または頻度が高すぎるバックグラウンドタスクは、バッテリーを消耗します。

EMRCA の原則(WWDC25-227 より)

バックグラウンド処理は、次の要件を満たす必要があります:

  • Efficient(効率的) — 軽量で目的に沿ったタスクを設計します
  • Minimal(最小限) — バックグラウンド処理を最小限に抑えます
  • Resilient(回復力がある) — 進捗を段階的に保存し、期限切れの通知に応答します
  • Courteous(配慮がある) — ユーザーの設定とシステムの状態を尊重します
  • Adaptive(適応性がある) — システムの優先順位を理解し、それに適応します
❌ アンチパターン — 長時間実行するバックグラウンドタスク
// BAD: Requests unlimited background time
// System will terminate after ~30 seconds anyway
var backgroundTask: UIBackgroundTaskIdentifier = .invalid

func applicationDidEnterBackground(_ application: UIApplication) {
    backgroundTask = application.beginBackgroundTask {
        // Expiration handler — but task runs too long
    }

    // Long operation that may not complete
    performLongOperation()
}
✅ 修正 — バックグラウンドタスクを適切に処理する
// GOOD: Finish quickly, save progress, notify system
var backgroundTask: UIBackgroundTaskIdentifier = .invalid

func applicationDidEnterBackground(_ application: UIApplication) {
    backgroundTask = application.beginBackgroundTask(withName: "Save State") { [weak self] in
        // Expiration handler — clean up immediately
        self?.saveProgress()
        if let task = self?.backgroundTask {
            application.endBackgroundTask(task)
        }
        self?.backgroundTask = .invalid
    }

    // Quick operation
    saveEssentialState()

    // End task as soon as done — don't wait for expiration
    application.endBackgroundTask(backgroundTask)
    backgroundTask = .invalid
}
✅ 長時間の処理には — BGProcessingTask を使用する
// BEST: Let system schedule at optimal time (charging, WiFi)
func scheduleBackgroundProcessing() {
    let request = BGProcessingTaskRequest(identifier: "com.app.maintenance")
    request.requiresNetworkConnectivity = true
    request.requiresExternalPower = true  // Only when charging

    try? BGTaskScheduler.shared.submit(request)
}

// Register handler at app launch
BGTaskScheduler.shared.register(
    forTaskWithIdentifier: "com.app.maintenance",
    using: nil
) { task in
    self.handleMaintenance(task: task as! BGProcessingTask)
}
✅ iOS 26 以降 — ユーザーが開始した処理には BGContinuedProcessingTask を使用する
// NEW iOS 26: Continue user-initiated tasks with progress UI
let request = BGContinuedProcessingTaskRequest(
    identifier: "com.app.export",
    title: "Exporting Photos",
    subtitle: "23 of 100 photos"
)

try? BGTaskScheduler.shared.submit(request)

パターン 6:フレームレートの監査

問題:補助アニメーションが必要以上に高いフレームレートで動作すると、GPU の消費電力が増加します。

❌ アンチパターン — フレームレートを制御しない
// BAD: Secondary animation runs at 60fps
// When primary content only needs 30fps, this wastes power
UIView.animate(withDuration: 2.0, delay: 0, options: [.repeat]) {
    self.subtitleLabel.alpha = 0.5
} completion: { _ in
    self.subtitleLabel.alpha = 1.0
}
✅ 修正 — CADisplayLink でフレームレートを制御する
// GOOD: Explicitly set preferred frame rate
let displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation))
displayLink.preferredFrameRateRange = CAFrameRateRange(
    minimum: 10,
    maximum: 30,  // Match primary content
    preferred: 30
)
displayLink.add(to: .current, forMode: .default)

WWDC22-10083 より:補助アニメーションのフレームレートをメインコンテンツに合わせることで、バッテリーを最大 20% 節約できます。


監査チェックリスト

タイマーの監査
  • すべてのタイマーに許容範囲が設定されていますか(間隔の 10% 以上)?
  • 不要になったタイマーは無効化されていますか?
  • 可能な場合、NSTimer ではなく Combine Timer を使用していますか?
  • プッシュ通知に置き換えられるポーリングのパターンはありませんか?
  • アプリがバックグラウンドに移行したとき、タイマーは停止しますか?
ネットワークの監査
  • 多数の小規模なリクエストではなく、リクエストをまとめていますか?
  • 緊急ではないダウンロードに、任意のタイミングで実行される URLSession を使用していますか?
  • 接続の試行が失敗しないよう、waitsForConnectivity を設定していますか?
  • 後回しにできる処理では、allowsExpensiveNetworkAccess を false に設定していますか?
  • ポーリングではなくプッシュ通知を使用していますか?
位置情報の監査
  • 適切な精度を使用していますか(ナビゲーション以外で kCLLocationAccuracyBest を使用していませんか)?
  • 更新頻度を下げるために distanceFilter を設定していますか?
  • 不要になったら更新を停止していますか?
  • バックグラウンド更新には大幅変更位置情報サービスを使用していますか?
  • バックグラウンドでの位置情報利用は正当化され、ユーザーに説明されていますか?
バックグラウンド実行の監査
  • 処理完了時に endBackgroundTask を速やかに呼び出していますか?
  • 長時間の処理では、requiresExternalPower を指定した BGProcessingTask を使用していますか?
  • Info.plist のバックグラウンドモードは、実際に必要なものに限定されていますか?
  • 再生していないときにオーディオセッションを無効化していますか?
  • EMRCA の原則に従っていますか?
ディスプレイ/GPU の監査
  • ダークモードに対応していますか(OLED の消費電力を70%削減)?
  • ビューが表示されていないときにアニメーションを停止していますか?
  • 補助的なアニメーションに適切なフレームレートを使用していますか?
  • ぼかし効果を最小限に抑えるか、削除していますか?
  • Metal のレンダリングにフレームレート制限を設けていますか?
ディスク I/O の監査
  • 頻繁な小規模書き込みではなく、書き込みをまとめて実行していますか?
  • SQLite で WAL ジャーナリングモードを使用していますか?
  • ファイルの作成と削除を短時間に繰り返さないようにしていますか?
  • 頻繁に更新するデータには、シリアル化されたファイルではなく SwiftData/Core Data を使用していますか?

負荷が高い状況のシナリオ

シナリオ 1:「リアルタイム更新のために、5秒ごとにポーリングすればいい」

誘惑:「プッシュ通知は複雑です。ポーリングのほうが簡単です。」

実情:

  • 5秒ごとのポーリング:無線通信が常時有効
  • プッシュ通知:データが変化したときだけ無線通信が有効
  • ユーザーはバッテリー設定で、あなたのアプリが上位に表示されていることに必ず気づきます
  • App Store のレビューには、必ず「バッテリーを食う」と書かれます

所要時間の比較:

  • ポーリングを実装する:30分
  • プッシュ通知を実装する:2~4時間
  • 悪いレビューと評判の低下への対処:数週間

反論のテンプレート:「プッシュ通知の設定には数時間かかりますが、ポーリングを使うと、バッテリー設定で上位に表示されることになります。ユーザーはバッテリーを消耗するアプリを積極的にアンインストールします。2時間の投資で、継続的な評判の低下を防げます。」


シナリオ 2:「最高の精度を得るために、位置情報を継続的に取得しよう」

誘惑:「ユーザーは正確な位置情報を期待しています。kCLLocationAccuracyBest を使いましょう。」

実情:

  • kCLLocationAccuracyBest:GPS+Wi-Fi+携帯電話の三角測量=大幅なバッテリー消費
  • kCLLocationAccuracyHundredMeters:ユースケースの95%で十分な精度
  • ステータスバーの位置情報アイコンを見て、ユーザーがバッテリー設定を確認します

所要時間の比較:

  • 高精度を実装する:10分
  • 「なぜこのアプリはバッテリーを消耗するのか」という苦情を調査する:数時間
  • 適切な精度にリファクタリングする:30分

反論のテンプレート:「[ユースケース] では100メートルの精度で十分です。Google Maps のようなナビゲーションアプリには最高の精度が必要ですが、私たちが表示するのは [店舗の場所/天気/おおよその地域] です。ユーザーには精度の違いがわかりませんが、バッテリー消費量の違いは非常に大きくなります。」


シナリオ 3:「ユーザーは滑らかな UI を期待しているので、アニメーションを動かし続けよう」

誘惑:「アニメーションがあると、アプリに活気が出て、洗練された印象になります。」

実情:

  • ビューが表示されていない間もアニメーションを動かし続けるのは、まったくの無駄です
  • 補助的なアニメーションのフレームレートが高いと、GPU の電力を消費します
  • GPU の消費電力は、デバイス全体の消費電力の大きな割合を占めます

所要時間の比較:

  • アニメーションを追加する:15分
  • 表示状態の確認を追加する:さらに5分
  • 「スマートフォンが熱くなる」という報告を調査する:数時間

反論のテンプレート:「アニメーションは維持できますが、ビューが表示されていないときは一時停止すべきです。これは5分で対応でき、ユーザーが画面を見ていない間の GPU の電力消費を防げます。」

シナリオ 4:「まず出荷し、最適化は後で」

誘惑:「エネルギー最適化は仕上げです。v1.1で対応できます。」

実情:

  • バッテリーの消耗はユーザーにすぐに気づかれます
  • 第一印象はレビューに影響します
  • 「バッテリーを大量に消費する」という評判はなかなか払拭できません
  • Power Profilerでのベースライン確認にかかる時間は15分です

所要時間の比較:

  • リリース前のPower Profilerチェック:15分
  • リリース後のエネルギー問題の修正:数日(さらに評判も損なわれます)
  • ユーザーの信頼を取り戻す:数か月

反論用テンプレート:「リリース前にPower Profilerで15分確認すれば、重大なエネルギー問題を見つけられます。バッテリーに問題がある状態で出荷すると、初日からユーザーのバッテリー設定の最上位に表示され、星1つのレビューが付くでしょう。簡単に確認させてください。問題が起きてから対処するより早く済みます。」


実例

例 1:先行読み込みを行う動画ストリーミングアプリ(WWDC25-226)

症状:ライブラリペインを開くと、CPUの電力への影響が1から21に跳ね上がりました。UIが固まりました。

Power Profilerを使った診断:

  1. ライブラリペインを開くときのトレースを記録しました
  2. CPU Power Impactレーンに大きなスパイクが表示されました
  3. Time Profilerで、VideoCardViewのbodyが何百回も呼び出されていることがわかりました
  4. 根本原因:VStackがすべての動画サムネイルを一度に作成していました

修正:

// Before: VStack (eager)
VStack {
    ForEach(videos) { video in
        VideoCardView(video: video)
    }
}

// After: LazyVStack (on-demand)
LazyVStack {
    ForEach(videos) { video in
        VideoCardView(video: video)
    }
}

結果:CPUの電力への影響は21から4.3に低下しました。UIが固まらなくなりました。


例 2:位置情報に基づく提案での繰り返しの解析(WWDC25-226)

症状:通勤中のユーザーから、バッテリーが大幅に消耗するとの報告がありました。開発者はデスクでは再現できませんでした。

デバイス上のPower Profilerを使った診断:

  1. ユーザーが通勤中にトレースを収集しました(設定 → デベロッパ → パフォーマンストレース)
  2. トレースでは、移動に連動した周期的なCPUスパイクが確認されました
  3. Time Profilerで、videoSuggestionsForLocationがCPUを消費していることがわかりました
  4. 根本原因:位置情報が更新されるたびにJSONファイルを解析していました

修正:

// Before: Parse on every call
func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    let data = try? Data(contentsOf: rulesFileURL)
    let rules = try? JSONDecoder().decode([RecommendationRule].self, from: data)
    return filteredVideos(using: rules)
}

// After: Parse once, cache
private lazy var cachedRules: [RecommendationRule] = {
    let data = try? Data(contentsOf: rulesFileURL)
    return (try? JSONDecoder().decode([RecommendationRule].self, from: data)) ?? []
}()

func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    return filteredVideos(using: cachedRules)
}

結果:移動中のCPUスパイクがなくなり、バッテリーの消耗も解消されました。


例 3:オーディオセッションが常にアクティブな音楽アプリ

症状:音楽を再生していないときも、アプリがバッテリーを消耗します。

診断:

  1. Power Profilerで、バックグラウンドのCPUアクティビティが持続していることがわかりました
  2. 再生停止後もオーディオセッションがアクティブなままでした
  3. システムがオーディオハードウェアの電源をオンにしたままにしていました

修正:

// Before: Never deactivate
func playTrack(_ track: Track) {
    try? AVAudioSession.sharedInstance().setActive(true)
    player.play()
}

func stopPlayback() {
    player.stop()
    // Audio session still active!
}

// After: Deactivate when done
func stopPlayback() {
    player.stop()
    try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}

// Even better: Use AVAudioEngine auto-shutdown
let engine = AVAudioEngine()
engine.isAutoShutdownEnabled = true  // Automatically powers down when idle

結果:バックグラウンドではオーディオハードウェアの電源がオフになり、バッテリーの消耗がなくなりました。


低電力モードへの対応

ユーザーが低電力モードを有効にしたときに検出し、動作を調整します。

// Check current state
if ProcessInfo.processInfo.isLowPowerModeEnabled {
    reduceEnergyUsage()
}

// React to changes
NotificationCenter.default.publisher(for: .NSProcessInfoPowerStateDidChange)
    .sink { [weak self] _ in
        if ProcessInfo.processInfo.isLowPowerModeEnabled {
            self?.reduceEnergyUsage()
        } else {
            self?.restoreNormalOperation()
        }
    }
    .store(in: &cancellables)

func reduceEnergyUsage() {
    // Pause optional activities
    // Reduce animation frame rates
    // Increase timer intervals
    // Defer network requests
    // Stop location updates if not critical
}

本番環境でのエネルギーのモニタリング

MetricKitの設定
import MetricKit

class EnergyMetricsManager: NSObject, MXMetricManagerSubscriber {
    static let shared = EnergyMetricsManager()

    func startMonitoring() {
        MXMetricManager.shared.add(self)
    }

    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            if let cpuMetrics = payload.cpuMetrics {
                // Monitor CPU time
                let foregroundCPU = cpuMetrics.cumulativeCPUTime
                logMetric("foreground_cpu", value: foregroundCPU)
            }

            if let locationMetrics = payload.locationActivityMetrics {
                // Monitor location usage
                let backgroundLocation = locationMetrics.cumulativeBackgroundLocationTime
                logMetric("background_location", value: backgroundLocation)
            }
        }
    }
}
Xcode Organizer

Xcode Organizerのバッテリー使用状況ペインで、実環境のデータを確認します:

  • フォアグラウンドとバックグラウンドのエネルギー消費の内訳
  • カテゴリ別の内訳(オーディオ、ネットワーク、処理、ディスプレイなど)
  • バージョンを比較して、リグレッションを検出

クイックリファレンス

Power Profilerのワークフロー
1. Connect device wirelessly
2. Product → Profile → Blank → Add Power Profiler
3. Record 2-3 minutes of usage
4. Identify dominant subsystem (CPU/GPU/Network/Display)
5. Apply targeted fix from patterns above
6. Record again to verify improvement
主なエネルギー節約策
最適化削減効果
OLEDでのダークモードディスプレイの消費電力を最大70%削減
フレームレートの調整GPUの消費電力を最大20%削減
プッシュとポーリングの使い分けネットワーク効率を100倍に向上
位置情報の精度を下げるGPSの消費電力を50~90%削減
タイマーの許容誤差CPUを大幅に節約
遅延読み込み起動時のCPUスパイクを解消
関連スキル
  • axiom-energy-ref — すべてのコード例を含むAPIリファレンス
  • axiom-energy-diag — 症状に基づくトラブルシューティングの判断ツリー
  • axiom-background-processing — バックグラウンドタスクの仕組み(タスクが実行されない理由)
  • axiom-performance-profiling — Instrumentsの一般的なワークフロー
  • axiom-memory-debugging — メモリリークの診断(エネルギー問題に関連することがよくあります)
  • axiom-networking — ネットワーク最適化のパターン
  • axiom-timer-patterns — タイマーのクラッシュ防止とライフサイクルの安全性

WWDC セッション

  • WWDC25-226 「アプリの電力使用量をプロファイルして最適化する」— 電力プロファイラのワークフロー
  • WWDC25-227 「タスクをバックグラウンドで完了する」— BGContinuedProcessingTask、EMRCA
  • WWDC22-10083 「電力を節約する:バッテリー消費を改善する」— ダークモード、フレームレート、延期
  • WWDC20-10095 「プッシュ通知の入門」— プッシュとポーリング
  • WWDC19-417 「バッテリー駆動時間とパフォーマンスの改善」— MetricKit

最終更新日: 2025-12-26 プラットフォーム: iOS 26+、iPadOS 26+ ステータス: 本番環境対応のエネルギー最適化パターン

コメント