Where does an app’s energy go, and how do timers, networking, location and animation change the battery bill?

21.6.1 · L3 · Interview

Predict first

What are a timer’s default tolerance, and the default network-access flags of a standard URLSession configuration?

import Foundation

let timer = Timer(timeInterval: 60, repeats: true) { _ in }
print("default tolerance:", timer.tolerance)
timer.tolerance = 6
print("after:", timer.tolerance)

let defaults = URLSessionConfiguration.default
print(defaults.isDiscretionary, defaults.waitsForConnectivity, defaults.allowsExpensiveNetworkAccess, defaults.allowsConstrainedNetworkAccess)
  • tolerance 0.0, then 6.0; discretionary false, waits false, expensive true, constrained true
  • tolerance 6.0 by default
  • discretionary true by default

Mental model. Making a hundred tiny trips to the shop uses far more fuel than one trip with a list, even if each trip carries a single item.

The big spenders are the CPU and GPU (sustained work, and especially waking from idle again and again), the radios (cellular and Wi-Fi), location (GPS, high accuracy), and the display. The key idea is that wake-ups cost more than they appear: after a transfer or a burst of work the hardware stays in a high-power state for a while before sleeping, so ten small requests spread over a minute cost far more than ten requests sent together. That is why a repeating Timer that fires every second, a polling loop, or a stream of tiny uploads drains the battery even when each does almost nothing.

Practical rules. Timers: avoid polling; when you must repeat, set tolerance (it defaults to 0, and a tolerance of about 10% of the interval lets the system coalesce your fire time with others) and invalidate timers when the screen goes away. Networking: batch and coalesce requests, prefer push to polling, give non-urgent transfers a background URLSession with isDiscretionary so the system chooses a good moment, honour allowsConstrainedNetworkAccess (Low Data Mode) and allowsExpensiveNetworkAccess, and set waitsForConnectivity instead of retrying in a loop. Location: ask for the lowest accuracy that works, prefer significant-change or region monitoring to continuous updates, and stop updates when finished (Q376). Animation and rendering: stop animations and display links when off screen and avoid constantly redrawing. Measure on a device with the Energy gauge in Xcode and the Power Profiler instrument, because the simulator draws no power.

A timer has no tolerance unless you set one, and a default session allows expensive and constrained networks and is neither discretionary nor waiting.

Common traps

  • A repeating timer with zero tolerance that never stops. It forces a wake-up on its own schedule for as long as the app runs.
  • Polling a server every few seconds. Use push, or a long interval with backoff.

They might follow up with

  • What is a discretionary session? A background transfer the system can schedule at an efficient time, such as on Wi-Fi and power.
  • Why does a tolerance help? It lets the system fire several timers in one wake-up instead of waking separately for each.

Related questions

  • 21.6.2 How should an app respond to Low Power Mode and thermal pressure, and how do background work and the tooling…
  • 21.2.1 Which Instruments templates do you reach for, how do you read a Time Profiler trace, and where can a sampling…
  • 17.2.1 How does Core Location work: authorization levels, accuracy, significant-change and visits, and geofencing?
  • 20.5.1 How do you design for the Digital Crown, the always-on display and the watch’s battery and CPU limits?

How should an app respond to Low Power Mode and thermal pressure, and… →