Skillshub axiom-energy
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
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/CharlesWiltgen/Axiom/axiom-energy" ~/.claude/skills/comeonoliver-skillshub-axiom-energy && rm -rf "$T"
skills/CharlesWiltgen/Axiom/axiom-energy/SKILL.mdEnergy 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
// 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
// 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
// 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
// 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
// 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:
{ "aps": { "content-available": 1 }, "custom-data": "your-payload" }
Key points:
- Background pushes are discretionary — system delivers at optimal time
- Use
for non-urgent updates (energy efficient)apns-priority: 5 - Use
only for time-sensitive alertsapns-priority: 10 - 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)
// 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
// 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)
// 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
// 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
for large collectionsLazyVGrid - 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
// 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
// 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
// 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 |
|---|---|---|
| Very High | Navigation apps only |
| High | Fitness tracking |
| Medium | Store locators |
| 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:
- Efficient — Design lightweight, purpose-driven tasks
- Minimal — Keep background work to a minimum
- Resilient — Save incremental progress; respond to expiration signals
- Courteous — Honor user preferences and system conditions
- Adaptive — Understand and adapt to system priorities
❌ Anti-Pattern — Long-running background task
// 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
// 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
// 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
// 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
// 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
// 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?
-
set to avoid failed connection attempts?waitsForConnectivity -
set to false for deferrable work?allowsExpensiveNetworkAccess - Push notifications instead of polling?
Location Audit
- Using appropriate accuracy (not
unless navigation)?kCLLocationAccuracyBest -
set to reduce update frequency?distanceFilter - Stopping updates when no longer needed?
- Using significant-change for background updates?
- Background location justified and explained to users?
Background Execution Audit
-
called promptly when work completes?endBackgroundTask - Long operations use
withBGProcessingTask
?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:
: GPS + WiFi + Cellular triangulation = massive drainkCLLocationAccuracyBest
: Good enough for 95% of use caseskCLLocationAccuracyHundredMeters- 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:
- Recorded trace while opening Library pane
- CPU Power Impact lane showed massive spike
- Time Profiler showed
body called hundreds of timesVideoCardView - Root cause:
creating ALL video thumbnails upfrontVStack
Fix:
// 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:
- User collected trace during commute (Settings → Developer → Performance Trace)
- Trace showed periodic CPU spikes correlating with movement
- Time Profiler showed
consuming CPUvideoSuggestionsForLocation - Root cause: JSON file parsed on EVERY location update
Fix:
// 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:
- Power Profiler showed sustained background CPU activity
- Audio session remained active after playback stopped
- System kept audio hardware powered on
Fix:
// 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:
// 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
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
— Complete API reference with all code examplesaxiom-energy-ref
— Symptom-based troubleshooting decision treesaxiom-energy-diag
— Background task mechanics (why tasks don't run)axiom-background-processing
— General Instruments workflowsaxiom-performance-profiling
— Memory leak diagnosis (often related to energy)axiom-memory-debugging
— Network optimization patternsaxiom-networking
— Timer crash prevention and lifecycle safetyaxiom-timer-patterns
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