---
name: axiom-energy
description: Use when app drains battery, device gets hot, users report energy issues, or auditing power consumption - systematic Power Profiler diagnosis, subsystem identification (CPU/GPU/Network/Location/Display), anti-pattern fixes for iOS/iPadOS
license: MIT
metadata:
  version: "1.0.0"
---

# Energy Optimization

## Overview

Energy issues manifest as battery drain, hot devices, and poor App Store reviews. **Core principle**: Measure before optimizing. Use Power Profiler to identify the dominant subsystem (CPU/GPU/Network/Location/Display), then apply targeted fixes.

**Key insight**: Developers often don't know where to START auditing. This skill provides systematic diagnosis, not guesswork.

**Requirements**: iOS 26+, Xcode 26+, Power Profiler in Instruments

## Example Prompts

Real questions developers ask that this skill answers:

#### 1. "My app is always at the top of Battery Settings. How do I find what's draining power?"
→ The skill covers Power Profiler workflow to identify dominant subsystem and targeted fixes

#### 2. "Users report my app makes their phone hot. Where do I start debugging?"
→ The skill provides decision tree: CPU vs GPU vs Network diagnosis with specific patterns

#### 3. "I have timers and location updates. Are they causing battery drain?"
→ The skill covers timer tolerance, location accuracy trade-offs, and audit checklists

#### 4. "My app drains battery in the background even when users aren't using it."
→ The skill covers background execution patterns, BGTasks, and EMRCA principles

#### 5. "How do I measure if my optimization actually improved battery life?"
→ The skill demonstrates before/after Power Profiler comparison workflow

---

## Red Flags — High Energy Likely

If you see ANY of these, suspect energy inefficiency:

- **Battery Settings**: Your app consistently at top of battery consumers
- **Device temperature**: Phone gets warm during normal app use
- **User reviews**: Mentions of "battery drain", "hot phone", "kills my battery"
- **Xcode Energy Gauge**: Shows sustained high or very high impact
- **Background runtime**: App runs longer than expected when not visible
- **Network activity**: Frequent small requests instead of batched operations
- **Location icon**: Appears in status bar when app shouldn't need location

#### Difference from normal energy use
- **Normal**: App uses energy during active use, minimal when backgrounded
- **Problem**: App uses significant energy even when user isn't interacting

## Mandatory First Steps

**ALWAYS run Power Profiler FIRST** before optimizing code:

### Step 1: Record a Power Trace (5 minutes)

```
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
```

**Why wireless**: When device is charging via cable, power metrics show 0. Use wireless debugging for accurate readings.

### Step 2: Identify Dominant Subsystem

Expand the Power Profiler track and examine per-app metrics:

| Lane | Meaning | High Value Indicates |
|------|---------|---------------------|
| CPU Power Impact | Processor activity | Computation, timers, parsing |
| GPU Power Impact | Graphics rendering | Animations, blur, Metal |
| Display Power Impact | Screen usage | Brightness, always-on content |
| Network Power Impact | Radio activity | Requests, downloads, polling |

**Look for**: Which subsystem shows highest sustained values during your app's usage.

### Step 3: Branch to Subsystem-Specific Fixes

Once you identify the dominant subsystem, use the decision trees below.

#### What this tells you
- **CPU dominant** → Check timers, polling, JSON parsing, eager loading
- **GPU dominant** → Check animations, blur effects, frame rates
- **Network dominant** → Check request frequency, polling vs push
- **Display dominant** → Check Dark Mode, brightness, screen-on time
- **Location** (shown in CPU) → Check accuracy, update frequency

#### Why diagnostics first
- Finding root cause with Power Profiler: **15-20 minutes**
- Guessing and testing random optimizations: **4+ hours, often wrong subsystem**

---

## Energy Decision Tree

```
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
```

---

## Common Energy Patterns (With Fixes)

### Pattern 1: Timer Efficiency

**Problem**: Timers wake the CPU from idle states, consuming significant energy.

#### ❌ Anti-Pattern — Timer without tolerance
```swift
// 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()
}
```

#### ✅ Fix — Set tolerance for timer batching
```swift
// 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 — Use event-driven instead of polling
```swift
// BEST: Don't use timer at all — react to events
NotificationCenter.default.publisher(for: .dataDidUpdate)
    .sink { [weak self] _ in
        self?.updateUI()
    }
    .store(in: &cancellables)
```

**Key points**:
- Set tolerance to **at least 10%** of interval
- Timer tolerance allows system to batch multiple timers into single wake
- Prefer event-driven patterns over polling timers
- Always invalidate timers when no longer needed

---

### Pattern 2: Push vs Poll

**Problem**: Polling (checking server every N seconds) keeps radios active and drains battery.

#### ❌ Anti-Pattern — Polling every 5 seconds
```swift
// 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
}
```

#### ✅ Fix — Use background push notifications
```swift
// 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)
        }
    }
}
```

**Server payload for background push**:
```json
{
    "aps": {
        "content-available": 1
    },
    "custom-data": "your-payload"
}
```

**Key points**:
- Background pushes are **discretionary** — system delivers at optimal time
- Use `apns-priority: 5` for non-urgent updates (energy efficient)
- Use `apns-priority: 10` only for time-sensitive alerts
- Polling every 5 seconds uses **100x more energy** than push

---

### Pattern 3: Lazy Loading & Caching

**Problem**: Loading all data upfront causes CPU spikes and memory pressure.

#### ❌ Anti-Pattern — Eager loading (from WWDC25-226)
```swift
// 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
    }
}
```

#### ✅ Fix — Lazy loading
```swift
// 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
    }
}
```

#### ❌ Anti-Pattern — Repeated parsing (from WWDC25-226)
```swift
// 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)
}
```

#### ✅ Fix — Cache parsed data
```swift
// 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!
}
```

**Key points**:
- Use `LazyVStack`, `LazyHStack`, `LazyVGrid` for large collections
- Cache parsed JSON, decoded data, computed results
- Move expensive operations out of frequently-called methods

---

### Pattern 4: Location Efficiency

**Problem**: Continuous location updates keep GPS active, draining battery rapidly.

#### ❌ Anti-Pattern — Continuous high-accuracy updates
```swift
// BAD: Continuous updates with best accuracy
// GPS stays active constantly, massive battery drain
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()  // Never stops!
```

#### ✅ Fix — Appropriate accuracy and significant-change
```swift
// 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()
}
```

#### ✅ Better — iOS 26+ CLLocationUpdate with stationary detection
```swift
// 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)
}
```

**Accuracy comparison (battery impact)**:
| Accuracy | Battery Impact | Use Case |
|----------|---------------|----------|
| `kCLLocationAccuracyBest` | Very High | Navigation apps only |
| `kCLLocationAccuracyNearestTenMeters` | High | Fitness tracking |
| `kCLLocationAccuracyHundredMeters` | Medium | Store locators |
| `kCLLocationAccuracyKilometer` | Low | Weather apps |
| Significant-change | Very Low | Background updates |

---

### Pattern 5: Background Execution (EMRCA)

**Problem**: Background tasks that run too long or too often drain battery.

#### EMRCA Principles (from WWDC25-227)

Your background work must be:
- **E**fficient — Design lightweight, purpose-driven tasks
- **M**inimal — Keep background work to a minimum
- **R**esilient — Save incremental progress; respond to expiration signals
- **C**ourteous — Honor user preferences and system conditions
- **A**daptive — Understand and adapt to system priorities

#### ❌ Anti-Pattern — Long-running background task
```swift
// 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()
}
```

#### ✅ Fix — Proper background task handling
```swift
// 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
}
```

#### ✅ For Long Operations — Use BGProcessingTask
```swift
// 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 for user-initiated work
```swift
// 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)
```

---

### Pattern 6: Frame Rate Auditing

**Problem**: Secondary animations running at higher frame rates than needed increase GPU power.

#### ❌ Anti-Pattern — Uncontrolled frame rates
```swift
// 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
}
```

#### ✅ Fix — Control frame rate with CADisplayLink
```swift
// 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)
```

**From WWDC22-10083**: Up to **20% battery savings** by aligning secondary animation frame rates with primary content.

---

## Audit Checklists

### Timer Audit
- [ ] All timers have tolerance set (≥10% of interval)?
- [ ] Timers invalidated when no longer needed?
- [ ] Using Combine Timer instead of NSTimer where possible?
- [ ] No polling patterns that could use push notifications?
- [ ] Timers stopped when app enters background?

### Network Audit
- [ ] Requests batched instead of many small requests?
- [ ] Using discretionary URLSession for non-urgent downloads?
- [ ] `waitsForConnectivity` set to avoid failed connection attempts?
- [ ] `allowsExpensiveNetworkAccess` set to false for deferrable work?
- [ ] Push notifications instead of polling?

### Location Audit
- [ ] Using appropriate accuracy (not `kCLLocationAccuracyBest` unless navigation)?
- [ ] `distanceFilter` set to reduce update frequency?
- [ ] Stopping updates when no longer needed?
- [ ] Using significant-change for background updates?
- [ ] Background location justified and explained to users?

### Background Execution Audit
- [ ] `endBackgroundTask` called promptly when work completes?
- [ ] Long operations use `BGProcessingTask` with `requiresExternalPower`?
- [ ] Background modes in Info.plist limited to what's actually needed?
- [ ] Audio session deactivated when not playing?
- [ ] EMRCA principles followed?

### Display/GPU Audit
- [ ] Dark Mode supported (70% OLED power savings)?
- [ ] Animations stopped when view not visible?
- [ ] Secondary animations use appropriate frame rates?
- [ ] Blur effects minimized or removed?
- [ ] Metal rendering has frame limiting?

### Disk I/O Audit
- [ ] Writes batched instead of frequent small writes?
- [ ] SQLite using WAL journaling mode?
- [ ] Avoiding rapid file creation/deletion?
- [ ] Using SwiftData/Core Data instead of serialized files for frequent updates?

---

## Pressure Scenarios

### Scenario 1: "Just poll every 5 seconds for real-time updates"

**The temptation**: "Push notifications are complex. Polling is simpler."

**The reality**:
- Polling every 5 seconds: Radio active **100% of time**
- Push notifications: Radio active **only when data changes**
- Users WILL see your app at top of Battery Settings
- App Store reviews WILL mention "battery hog"

**Time cost comparison**:
- Implement polling: 30 minutes
- Implement push: 2-4 hours
- Fix bad reviews + reputation damage: Weeks

**Pushback template**: "Push notification setup takes a few hours, but polling will guarantee we're at the top of Battery Settings. Users actively uninstall apps that drain battery. The 2-hour investment prevents ongoing reputation damage."

---

### Scenario 2: "Use continuous location for best accuracy"

**The temptation**: "Users expect accurate location. Let's use `kCLLocationAccuracyBest`."

**The reality**:
- `kCLLocationAccuracyBest`: GPS + WiFi + Cellular triangulation = **massive drain**
- `kCLLocationAccuracyHundredMeters`: Good enough for 95% of use cases
- Location icon in status bar = users checking Battery Settings

**Time cost comparison**:
- Implement high accuracy: 10 minutes
- Debug "why does my app drain battery" complaints: Hours
- Refactor to appropriate accuracy: 30 minutes

**Pushback template**: "100-meter accuracy is sufficient for [use case]. Navigation apps like Google Maps need best accuracy, but we're showing [store locations / weather / general area]. The accuracy difference is imperceptible to users, but battery difference is massive."

---

### Scenario 3: "Keep animations running, users expect smooth UI"

**The temptation**: "Animations make the app feel alive and polished."

**The reality**:
- Animations running when view not visible = pure waste
- High frame rate secondary animations = GPU drain
- GPU power is significant portion of total device power

**Time cost comparison**:
- Add animation: 15 minutes
- Add visibility checks: 5 minutes extra
- Debug "phone gets hot" reports: Hours

**Pushback template**: "We can keep the animation, but should pause it when the view isn't visible. This is a 5-minute change that prevents GPU drain when users aren't looking at the screen."

---

### Scenario 4: "Ship now, optimize later"

**The temptation**: "Energy optimization is polish. We can do it in v1.1."

**The reality**:
- Battery drain is **immediately visible** to users
- First impressions drive reviews
- "Battery hog" reputation is hard to shake
- Power Profiler baseline takes **15 minutes**

**Time cost comparison**:
- Power Profiler check before launch: 15 minutes
- Fix energy issues post-launch: Days (plus reputation damage)
- Regain user trust: Months

**Pushback template**: "A 15-minute Power Profiler session before launch catches major energy issues. If we ship with battery problems, users will see us at top of Battery Settings on day one and leave 1-star reviews. Let me do a quick check — it's faster than damage control."

---

## Real-World Examples

### Example 1: Video Streaming App with Eager Loading (WWDC25-226)

**Symptom**: CPU power impact jumped from 1 to 21 when opening Library pane. UI hung.

**Diagnosis using Power Profiler**:
1. Recorded trace while opening Library pane
2. CPU Power Impact lane showed massive spike
3. Time Profiler showed `VideoCardView` body called hundreds of times
4. Root cause: `VStack` creating ALL video thumbnails upfront

**Fix**:
```swift
// Before: VStack (eager)
VStack {
    ForEach(videos) { video in
        VideoCardView(video: video)
    }
}

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

**Result**: CPU power impact dropped from 21 to 4.3. UI no longer hung.

---

### Example 2: Location-Based Suggestions with Repeated Parsing (WWDC25-226)

**Symptom**: User commuting reported massive battery drain. Developer couldn't reproduce at desk.

**Diagnosis using on-device Power Profiler**:
1. User collected trace during commute (Settings → Developer → Performance Trace)
2. Trace showed periodic CPU spikes correlating with movement
3. Time Profiler showed `videoSuggestionsForLocation` consuming CPU
4. Root cause: JSON file parsed on EVERY location update

**Fix**:
```swift
// 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)
}
```

**Result**: Eliminated CPU spikes during movement. Battery drain resolved.

---

### Example 3: Music App with Always-Active Audio Session

**Symptom**: App drains battery even when not playing music.

**Diagnosis**:
1. Power Profiler showed sustained background CPU activity
2. Audio session remained active after playback stopped
3. System kept audio hardware powered on

**Fix**:
```swift
// 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
```

**Result**: Background audio hardware powered down. Battery drain eliminated.

---

## Responding to Low Power Mode

Detect and adapt when user enables Low Power Mode:

```swift
// 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
}
```

---

## Monitoring Energy in Production

### MetricKit Setup

```swift
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

Check **Battery Usage** pane in Xcode Organizer for field data:
- Foreground vs background energy breakdown
- Category breakdown (Audio, Networking, Processing, Display, etc.)
- Version comparison to detect regressions

---

## Quick Reference

### Power Profiler Workflow
```
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
```

### Key Energy Savings
| Optimization | Potential Savings |
|--------------|------------------|
| Dark Mode on OLED | Up to 70% display power |
| Frame rate alignment | Up to 20% GPU power |
| Push vs poll | 100x network efficiency |
| Location accuracy reduction | 50-90% GPS power |
| Timer tolerance | Significant CPU savings |
| Lazy loading | Eliminates startup CPU spikes |

### Related Skills
- `axiom-energy-ref` — Complete API reference with all code examples
- `axiom-energy-diag` — Symptom-based troubleshooting decision trees
- `axiom-background-processing` — Background task mechanics (why tasks don't run)
- `axiom-performance-profiling` — General Instruments workflows
- `axiom-memory-debugging` — Memory leak diagnosis (often related to energy)
- `axiom-networking` — Network optimization patterns
- `axiom-timer-patterns` — Timer crash prevention and lifecycle safety

---

## WWDC Sessions

- **WWDC25-226** "Profile and optimize power usage in your app" — Power Profiler workflow
- **WWDC25-227** "Finish tasks in the background" — BGContinuedProcessingTask, EMRCA
- **WWDC22-10083** "Power down: Improve battery consumption" — Dark Mode, frame rates, deferral
- **WWDC20-10095** "The Push Notifications primer" — Push vs poll
- **WWDC19-417** "Improving Battery Life and Performance" — MetricKit

---

**Last Updated**: 2025-12-26
**Platforms**: iOS 26+, iPadOS 26+
**Status**: Production-ready energy optimization patterns
