Skip to content
advanced Phase 11 · Performance & Optimization

Battery & Energy Optimization

Minimize energy usage: background tasks, location accuracy, and network efficiency.

45m
2 problems
Topic Progress 0%

Energy Diagnostics

Understanding Energy Impact

iOS monitors your app energy usage and classifies it as Minimal, Low, Medium, or High. The Energy Impact instrument in Instruments shows exactly what drains battery.

Major Energy Consumers

  • CPU usage: Sustained high CPU usage is the biggest battery drain
  • Network activity: Radio is power-hungry, especially cellular
  • Location services: GPS is extremely energy-intensive
  • Display: Screen-on time and high frame rates consume power
  • Background activity: Tasks running while app is suspended

Measuring Energy Impact

In Xcode, go to Product, Profile, then select Energy Log instrument. It shows:

  • CPU usage over time
  • Network bytes sent and received
  • Location updates frequency
  • Background task execution
  • Battery level changes

Common Energy Pitfalls

// Bad: Polling with Timer
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    fetchUpdates()  // Wakes CPU every second
}

// Good: Use Push Notifications or Background Tasks
func scheduleRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "refresh")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 3600)
    try? BGTaskScheduler.shared.submit(request)
}

Responding to Low Power Mode

NotificationCenter.default.addObserver(
    forName: .NSProcessInfoPowerStateDidChange,
    object: nil,
    queue: .main
) { _ in
    if ProcessInfo.processInfo.isLowPowerModeEnabled {
        // Reduce animation frame rate
        // Stop non-essential background tasks
        // Reduce network frequency
    } else {
        // Resume normal behavior
    }
}

Background Tasks

BGTaskScheduler

iOS provides BGTaskScheduler for efficient background work. The system batches tasks to minimize wake-ups:

// Register task handler
BGTaskScheduler.shared.register(
    forTaskWithIdentifier: "com.app.refresh",
    using: nil
) { task in
    self.handleRefresh(task: task as! BGAppRefreshTask)
}

// Schedule a refresh
func scheduleRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
    try? BGTaskScheduler.shared.submit(request)
}

// Handle the task
func handleRefresh(task: BGAppRefreshTask) {
    let operation = RefreshOperation()
    task.expirationHandler = { operation.cancel() }
    
    operation.completion = { success in
        task.setTaskCompleted(success: success)
    }
    
    OperationQueue.main.addOperation(operation)
    scheduleRefresh() // Schedule next refresh
}

Background App Refresh

Enable Background App Refresh in Settings and use it wisely:

  • Update content that changes frequently (news, social feeds)
  • Sync data that the user expects to be current
  • Pre-fetch content the user will likely need

Processing vs Refresh Tasks

  • BGAppRefreshTask: Lightweight updates, limited time
  • BGProcessingTask: Heavy work like downloads, sync, analysis. Requires Background Mode capability.
// Processing task for heavy work
let request = BGProcessingTaskRequest(identifier: "com.app.sync")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = false
try? BGTaskScheduler.shared.submit(request)

Avoid Excessive Background Work

iOS may terminate apps that abuse background time. Follow these guidelines:

  • Complete background work quickly (typically under 30 seconds)
  • Schedule tasks at reasonable intervals
  • Respect the system scheduling decisions
  • Always schedule the next task at the end of the current one

Network Efficiency

The Cost of Network Requests

Every network request activates the cellular or WiFi radio, which stays active for several seconds after the last byte. Multiple small requests waste energy because the radio cannot power down between them.

Batching Requests

// Bad: Three separate requests
async func loadDashboard() async throws -> Dashboard {
    let user = try await api.fetchUser()
    let posts = try await api.fetchPosts()
    let notifications = try await api.fetchNotifications()
    return Dashboard(user: user, posts: posts, notifications: notifications)
}

// Good: Single combined request
async func loadDashboard() async throws -> Dashboard {
    return try await api.fetchDashboard() // One request returns all data
}

// Or parallel requests (still better than sequential)
func loadDashboard() async throws -> Dashboard {
    async let user = api.fetchUser()
    async let posts = api.fetchPosts()
    async let notifications = api.fetchNotifications()
    return try await Dashboard(
        user: user, posts: posts, notifications: notifications
    )
}

Caching Strategy

class NetworkCache {
    private let cache = NSCache<NSString, CachedResponse>()
    private let session: URLSession
    
    func fetch(_ request: URLRequest) async throws -> Data {
        let key = request.url!.absoluteString as NSString
        
        if let cached = cache.object(forKey: key),
           !cached.isExpired {
            return cached.data
        }
        
        let (data, _) = try await session.data(for: request)
        cache.setObject(
            CachedResponse(data: data, expiry: Date().addingTimeInterval(300)),
            forKey: key
        )
        return data
    }
}

Cellular vs WiFi

Check network type and adjust behavior:

import Network

class NetworkMonitor {
    static let shared = NetworkMonitor()
    private let monitor = NWPathMonitor()
    private(set) var connectionType: ConnectionType = .unknown
    
    enum ConnectionType { case wifi, cellular, unknown }
    
    func start() {
        monitor.pathUpdateHandler = { [weak self] path in
            if path.usesInterfaceType(.wifi) {
                self?.connectionType = .wifi
            } else if path.usesInterfaceType(.cellular) {
                self?.connectionType = .cellular
            }
        }
        monitor.start(queue: .global())
    }
}

Download high-quality content on WiFi, defer large downloads on cellular, and use compressed formats when on cellular.

Quiz

1. Which is the most energy-intensive operation on iOS?

Question 1 options

2. What does BGTaskScheduler provide?

Question 2 options

3. Why is batching network requests better for battery?

Question 3 options

4. How should you respond to Low Power Mode?

Question 4 options

Flashcards

Question

What are the main energy consumers in an iOS app?

Answer

CPU usage, network activity (especially cellular radio), GPS location services, display brightness, and background tasks.

Question

What is the difference between BGAppRefreshTask and BGProcessingTask?

Answer

BGAppRefreshTask is for lightweight quick updates. BGProcessingTask is for heavier work like downloads and sync, requiring Background Mode capability.

Question

How do you check for Low Power Mode in code?

Answer

Use ProcessInfo.processInfo.isLowPowerModeEnabled to read the state, and observe .NSProcessInfoPowerStateDidChange for changes.

Revision Notes

Key Takeaways

  • 1. GPS location tracking is the biggest battery drain
  • 2. Batch network requests to reduce radio wake-ups
  • 3. BGTaskScheduler provides system-managed background execution
  • 4. Respond to Low Power Mode by reducing activity
  • 5. Always test energy impact with Instruments Energy Log

Interview Tips

  • Discuss strategies for reducing battery consumption
  • Explain how BGTaskScheduler works and its limitations
  • Describe how you would handle Low Power Mode in your app
  • Walk through optimizing a feature for energy efficiency

Cheat Sheet

Battery Optimization Quick Reference

  • GPS is the most energy-intensive operation
  • Batch network requests to reduce radio wake-ups
  • Use BGTaskScheduler for background work
  • Respond to Low Power Mode changes
  • Defer heavy work to when device is charging
  • Cache network responses appropriately