Do something every x minutes in Swift
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Running code every few minutes in Swift is straightforward while your app is active, but the right tool depends on where the work runs and how exact the schedule needs to be. The main distinction is between ordinary in-process timers and true background execution managed by iOS.
Use Timer for Run-Loop-Based Work
If the task belongs to an active screen, view model, or other main-thread object, Timer is the simplest option. It integrates with the run loop and is a good fit for UI refresh, lightweight polling, or periodic state updates while the app is in the foreground.
This approach works well when "every five minutes" means "while this object is alive and the app is running." It does not promise exact wall-clock precision. Run-loop activity, device load, and app lifecycle transitions can shift the callback slightly.
That is usually fine for UI refresh or soft polling. It is not fine for compliance-grade scheduling, alarms, or work that must happen at an exact real-world time.
Use DispatchSourceTimer for Queue Control
When the periodic task should run on a specific dispatch queue rather than the main run loop, DispatchSourceTimer gives you more control. That is useful for file processing, background parsing, or network preparation that should not block UI work.
The leeway parameter is worth paying attention to. It tells the system how much flexibility it has when scheduling the wakeup, which can improve battery efficiency. For most polling jobs, a small amount of leeway is a better tradeoff than chasing perfect timing.
Manage Timer Ownership Explicitly
The most common bug is not the timer API itself, but ownership. Repeating timers continue until they are invalidated or cancelled. If a screen appears twice and each appearance creates a new timer, the task may run in duplicate. If the closure strongly captures its owner, you can also create a retain cycle.
That is why timer setup should be paired with deliberate cleanup. In UIKit, that often means starting and stopping in lifecycle methods. In SwiftUI, it usually means placing the timer inside a model object rather than scattering timer creation across view modifiers.
This pattern keeps the scheduling logic in one place and makes repeated view rendering less dangerous.
iOS Background Limits Matter More Than the API Choice
Many developers ask how to run code every x minutes when the real requirement is background refresh. A normal Timer or DispatchSourceTimer cannot keep firing after the app is suspended. Once iOS suspends your process, your timer stops because your code is no longer running.
If the actual goal is background work, use a background-specific mechanism:
- '
BGAppRefreshTaskfor lightweight refresh opportunities.' - '
BGProcessingTaskfor deferred heavier work.' - Push notifications when the server should trigger client activity.
- Server-side scheduling if the task does not need to live on the device.
That architectural distinction matters more than any specific timer call. A correctly written repeating timer is still not a background scheduler.
Design the Interval Strategy, Not Just the Interval
In production, the question is rarely only "how do I fire every five minutes." You also need to decide what happens when the previous run has not finished, when the network is down, or when thousands of devices poll simultaneously. Good periodic design often adds jitter, backoff, and overlap protection so the timer does not become a source of load spikes or duplicated work.
That design layer is what separates a demo timer from a production polling loop.
Common Pitfalls
- Expecting a timer to keep running while the iOS app is suspended.
- Starting multiple repeating timers for the same job.
- Doing heavy synchronous work inside a main-thread timer callback.
- Forgetting to invalidate or cancel the timer when the owner goes away.
- Treating a local timer as a substitute for proper background APIs.
Summary
- Use
Timerfor periodic foreground work tied to the run loop. - Use
DispatchSourceTimerwhen queue control matters. - Pair timer startup with explicit cleanup and avoid duplicate scheduling.
- Do not rely on timers for guaranteed background execution on iOS.
- Add backoff, jitter, and overlap control for real production polling.

