axiom-energy
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-energy
Agent 安装分布
Skill 文档
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
// 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
apns-priority: 5for non-urgent updates (energy efficient) - Use
apns-priority: 10only 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)
// 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,LazyVGridfor 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
// 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 |
|---|---|---|
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:
- 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?
-
waitsForConnectivityset to avoid failed connection attempts? -
allowsExpensiveNetworkAccessset to false for deferrable work? - Push notifications instead of polling?
Location Audit
- Using appropriate accuracy (not
kCLLocationAccuracyBestunless navigation)? -
distanceFilterset 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
-
endBackgroundTaskcalled promptly when work completes? - Long operations use
BGProcessingTaskwithrequiresExternalPower? - 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 drainkCLLocationAccuracyHundredMeters: 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:
- Recorded trace while opening Library pane
- CPU Power Impact lane showed massive spike
- Time Profiler showed
VideoCardViewbody called hundreds of times - Root cause:
VStackcreating ALL video thumbnails upfront
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
videoSuggestionsForLocationconsuming CPU - 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
axiom-energy-refâ Complete API reference with all code examplesaxiom-energy-diagâ Symptom-based troubleshooting decision treesaxiom-background-processingâ Background task mechanics (why tasks don’t run)axiom-performance-profilingâ General Instruments workflowsaxiom-memory-debuggingâ Memory leak diagnosis (often related to energy)axiom-networkingâ Network optimization 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