How should an app respond to Low Power Mode and thermal pressure, and how do background work and the tooling fit in?
Predict first
What are the thermal states’ raw values, and what do a Mac’s thermal state and Low Power Mode report now? Does begin/end activity run?
import Foundation
let s = ProcessInfo.ThermalState.self
print(s.nominal.rawValue, s.fair.rawValue, s.serious.rawValue, s.critical.rawValue)
print(ProcessInfo.processInfo.thermalState == .nominal, ProcessInfo.processInfo.isLowPowerModeEnabled)
print(Notification.Name.NSProcessInfoPowerStateDidChange.rawValue)
let token = ProcessInfo.processInfo.beginActivity(options: [.userInitiated], reason: "Exporting a document")
ProcessInfo.processInfo.endActivity(token)
print("activity ended")
- 0 1 2 3, then true false, then the notification name, then activity ended
- 3 2 1 0, then false true
- It crashes outside an app
Mental model. A runner who feels the heat slows down before collapsing. The thermal and power-state APIs are how your app notices it is overheating or short of fuel.
The system tells you when to back off. ProcessInfo.processInfo.isLowPowerModeEnabled says the user has asked to save battery (observe NSProcessInfoPowerStateDidChange), and thermalState reports heat as .nominal, .fair, .serious or .critical (observe ProcessInfo.thermalStateDidChangeNotification). A good app degrades gracefully: in Low Power Mode lower frame rates and animation, pause prefetching and auto-play, sync less often and lower image and video quality; at .serious or .critical reduce work such as camera, game or ML load instead of letting the system throttle you abruptly. The system itself reduces background activity in Low Power Mode, so do not rely on background refresh happening on schedule.
For background work use the tools that let the system choose the moment: BGTaskScheduler (Q224) for refresh and processing tasks that can require power and network, background URLSession transfers, and silent push for server-driven updates. When you do foreground-critical work that must not be interrupted (an export, a large save), wrap it in ProcessInfo.performActivity or beginActivity(options:reason:) and end the activity when done, which is polite because it tells the system exactly how long you need. Close the loop with data: Xcode’s Energy gauge and the Power Profiler for a device run, MetricKit and the Organizer for field energy, and CPU, GPU, location and networking breakdowns to see which subsystem dominates.
The four states run from nominal (0) to critical (3). A cool, plugged-in Mac is nominal with Low Power Mode off.
Common traps
- Ignoring Low Power Mode. Auto-playing video and aggressive prefetch are the first things a battery-conscious user expects you to stop.
- Leaving an activity open. A
beginActivitywithout anendActivitykeeps the system from managing your power.
They might follow up with
- How do you react to thermal state? Lower the quality and the amount of concurrent work, and restore it when the state returns to nominal.
- Why not poll the thermal state? Observe the notification; polling costs energy itself.
Related questions
- 21.6.1 Where does an app’s energy go, and how do timers, networking, location and animation change the battery bill?
- 21.3.1 What does MetricKit give you in production, how do you subscribe, and what is the difference between metric…
- 6.5.3 How does BGTaskScheduler work, and why does your task not run when you ask?
- 20.5.1 How do you design for the Digital Crown, the always-on display and the watch’s battery and CPU limits?
← Where does an app’s energy go, and how do timers, networking…