Practical examples of timer examples with Grand Central Dispatch in Swift
Real-world examples of timer examples with Grand Central Dispatch in Swift
Let’s skip the theory and start with actual code. All of these examples of timer examples with Grand Central Dispatch in Swift use DispatchSourceTimer, which is the go-to GCD API for timers.
Basic repeating GCD timer on the main queue
This is the classic example of a repeating timer that updates the UI every second, such as a countdown label or clock.
final class CountdownViewModel {
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue.main
private var remainingSeconds: Int
init(startSeconds: Int) {
self.remainingSeconds = startSeconds
}
func start(onTick: @escaping (Int) -> Void, onComplete: (() -> Void)? = nil) {
timer?.cancel()
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now(), repeating: 1.0)
timer.setEventHandler { [weak self] in
guard let self = self else { return }
self.remainingSeconds -= 1
onTick(self.remainingSeconds)
if self.remainingSeconds <= 0 {
self.timer?.cancel()
self.timer = nil
onComplete?()
}
}
self.timer = timer
timer.resume()
}
func stop() {
timer?.cancel()
timer = nil
}
}
This is a clean example of a UI-focused timer that:
- Runs on
DispatchQueue.mainso UI updates are safe. - Cancels itself when done.
- Avoids strong reference cycles with
[weak self].
Among the best examples of timer examples with Grand Central Dispatch in Swift, this pattern shows up constantly in countdowns, OTP screens, and workout timers.
Background GCD timer for periodic work
Now a more production-like example of work that should not run on the main queue: say you’re cleaning a cache every 10 minutes.
final class CacheCleaner {
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue(label: "com.example.cachecleaner")
func start() {
timer?.cancel()
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + 10 * 60, repeating: 10 * 60)
timer.setEventHandler { [weak self] in
self?.cleanExpiredItems()
}
self.timer = timer
timer.resume()
}
func stop() {
timer?.cancel()
timer = nil
}
private func cleanExpiredItems() {
// Heavy I/O or database cleanup here
}
}
This is a good example of a timer that:
- Runs on a dedicated serial queue.
- Handles background work without blocking the UI.
When people talk about real examples of timer examples with Grand Central Dispatch in Swift, this kind of periodic background maintenance is usually near the top of the list.
Debouncing user input with a GCD timer
Modern apps often debounce text input to avoid hammering APIs. For instance, you might wait 400 ms after the user stops typing before firing a search request.
final class SearchDebouncer {
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue(label: "com.example.searchdebounce")
func scheduleSearch(delay: TimeInterval = 0.4,
perform: @escaping () -> Void) {
timer?.cancel()
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + delay)
timer.setEventHandler {
perform()
}
self.timer = timer
timer.resume()
}
func cancel() {
timer?.cancel()
timer = nil
}
}
In UIKit or SwiftUI, you’d call scheduleSearch from your text field change handler. This example of debouncing is one of the best examples of timer examples with Grand Central Dispatch in Swift because it shows how to use a one-shot timer instead of a repeating one.
Rate-limiting network pings or analytics
Another pattern: you want to send analytics or heartbeat pings at most once every N seconds, even if events arrive more frequently.
final class HeartbeatScheduler {
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue(label: "com.example.heartbeat")
private var pending = false
func scheduleHeartbeat(interval: TimeInterval = 30,
send: @escaping () -> Void) {
pending = true
if timer == nil {
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now(), repeating: interval)
timer.setEventHandler { [weak self] in
guard let self = self else { return }
guard self.pending else { return }
self.pending = false
send()
}
self.timer = timer
timer.resume()
}
}
func stop() {
timer?.cancel()
timer = nil
}
}
Here, multiple calls to scheduleHeartbeat within the interval result in at most one send. These examples include stateful logic layered on top of a simple repeating timer, which is exactly how many production analytics systems behave.
GCD timer with tolerance for better battery life
Apple has long recommended using timer tolerance to help the system coalesce wakeups and save battery. While DispatchSourceTimer doesn’t have a direct tolerance property like Timer, you can approximate tolerance by rounding your schedule times.
For example, if you’re running a background data sync every 5 minutes, you can align to the nearest minute boundary:
extension DispatchSourceTimer {
static func makeAlignedTimer(interval: TimeInterval,
alignment: TimeInterval,
queue: DispatchQueue,
handler: @escaping () -> Void) -> DispatchSourceTimer {
let timer = DispatchSource.makeTimerSource(queue: queue)
let now = Date().timeIntervalSince1970
let nextAligned = ((now / alignment).rounded(.up)) * alignment
let delay = nextAligned - now
timer.schedule(deadline: .now() + delay, repeating: interval)
timer.setEventHandler(handler: handler)
return timer
}
}
// Usage
let queue = DispatchQueue(label: "com.example.sync")
let timer = DispatchSourceTimer.makeAlignedTimer(
interval: 5 * 60,
alignment: 60,
queue: queue
) {
// Perform sync
}
timer.resume()
While this isn’t a medical or health timer like those discussed on sites such as Mayo Clinic, the same principle applies: reducing unnecessary wakeups improves device “health” by saving battery and reducing CPU churn.
Swift concurrency and GCD timers living side by side
Even with Swift concurrency (async/await, Task), GCD timers are still very relevant in 2024–2025. You can bridge them into async code using continuations or by exposing async streams.
Here’s an example of wrapping a GCD timer as an AsyncSequence of ticks:
struct GCDTimerSequence: AsyncSequence {
typealias Element = Void
let interval: TimeInterval
struct AsyncIterator: AsyncIteratorProtocol {
private var continuation: AsyncThrowingStream<Void, Error>.Iterator
init(interval: TimeInterval) {
let stream = AsyncThrowingStream<Void, Error> { continuation in
let queue = DispatchQueue(label: "com.example.gcdtimersequence")
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + interval, repeating: interval)
timer.setEventHandler {
continuation.yield(())
}
continuation.onTermination = { _ in
timer.cancel()
}
timer.resume()
}
self.continuation = stream.makeAsyncIterator()
}
mutating func next() async throws -> Void? {
try await continuation.next()
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(interval: interval)
}
}
// Usage in an async context
Task {
for try await _ in GCDTimerSequence(interval: 1.0) {
print("Tick from async sequence")
}
}
This is one of the more advanced examples of timer examples with Grand Central Dispatch in Swift, showing how legacy GCD primitives still play nicely with the newer concurrency model.
Handling app backgrounding and invalidation
Real examples always include lifecycle edge cases. If you rely on timers, you have to think about what happens when the app moves to the background or is suspended.
A simple pattern:
final class LifecycleAwareTimer {
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue(label: "com.example.lifecycle")
func start() {
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now(), repeating: 2.0)
timer.setEventHandler {
print("Doing periodic work")
}
self.timer = timer
timer.resume()
NotificationCenter.default.addObserver(
self,
selector: #selector(appDidEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(appWillEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
}
@objc private func appDidEnterBackground() {
timer?.suspend()
}
@objc private func appWillEnterForeground() {
timer?.resume()
}
deinit {
NotificationCenter.default.removeObserver(self)
timer?.cancel()
}
}
This example of lifecycle-aware timers shows a common mistake: suspending a timer that’s already suspended will crash. In real apps, you’d track the suspension state to avoid double-suspends or double-resumes.
For background behavior, Apple’s official documentation on Energy Efficiency and background execution strategies is worth reading. While it’s not Swift-specific like these examples of timer examples with Grand Central Dispatch in Swift, it gives a broader picture of how timers affect battery and responsiveness.
Timers in health, fitness, and safety-related apps
If you’re building timers for health or safety use cases (for example, workout intervals, medication reminders, or cooldown periods), you need to think beyond just code:
- How accurate does the timer need to be?
- What happens if the app is killed?
- Do you need notifications or background tasks instead of pure in-memory timers?
Organizations like the National Institutes of Health and CDC publish guidance on adherence, reminders, and behavior patterns. While they won’t teach you GCD, they’re helpful context when you’re deciding whether an in-app timer is enough or if you need scheduled notifications, HealthKit integration, or even server-side scheduling.
In other words, the best examples of timer examples with Grand Central Dispatch in Swift inside health or fitness apps usually combine:
- A GCD timer while the app is in the foreground.
- Local notifications or background tasks to cover app suspension.
Patterns and best practices from these examples
Across all of these real examples of timer examples with Grand Central Dispatch in Swift, a few patterns repeat:
Always cancel timers you own
Every example of a GCD timer here has a stop or deinit cleanup path that calls cancel(). If you don’t cancel, the timer will keep its queue alive and may keep your object in memory longer than you expect.
Use the right queue for the job
- UI updates: main queue.
- I/O, networking, computation: background queues.
Mixing these leads to stutters or race conditions. In the examples of timer examples with Grand Central Dispatch in Swift above, you’ll notice a consistent separation.
Avoid retain cycles
Using [weak self] in setEventHandler is almost always the right move. Let the owner decide when the timer should die, not the other way around.
Prefer higher intervals when possible
From a performance and battery standpoint, fewer wakeups are better. Apple’s performance docs and general optimization principles (similar in spirit to energy and workload recommendations on sites like Harvard) all push in the same direction: do more work less often, and batch when you can.
FAQ: Timer examples with Grand Central Dispatch in Swift
Q: Can you give another simple example of a one-shot GCD timer in Swift?
Yes. A very small example of a one-shot timer that fires once after 2 seconds:
let queue = DispatchQueue.global(qos: .background)
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + 2.0)
timer.setEventHandler {
print("Fired once after 2 seconds")
timer.cancel()
}
timer.resume()
Q: When should I use GCD timers instead of Timer?
Use GCD timers when you need more control over the queue, better behavior in background work, or when you’re already using GCD heavily. Timer is fine for simple UI work, but many of the best examples of timer examples with Grand Central Dispatch in Swift come from scenarios where Timer’s runloop behavior becomes limiting.
Q: Are GCD timers accurate enough for health or safety-critical timing?
They’re good for most consumer use cases, but you should not rely on any in-app timer for medical or life-critical behavior. For that, you’d combine system notifications, background tasks, and server-side scheduling, and you’d follow guidance from health authorities like NIH or CDC around adherence and safety.
Q: Do these examples include support for Swift concurrency out of the box?
Most of the examples are GCD-first, but you saw how to wrap a GCD timer in an AsyncSequence. In modern apps, it’s common to keep GCD timers as the low-level primitive and expose async-friendly APIs on top.
Q: Are there any pitfalls when suspending and resuming GCD timers?
Yes. Calling resume() more times than suspend() (or vice versa) leads to crashes. In production, track the suspension state in a Boolean and guard your calls. Many real examples of timer examples with Grand Central Dispatch in Swift in open source projects include that extra state management for safety.
Related Topics
Practical examples of simple calculator app examples in Swift
Practical examples of Swift async/await examples for asynchronous programming
Best examples of practical examples of using closures in Swift
Practical examples of UITableView implementation in Swift for modern iOS apps
Practical examples of timer examples with Grand Central Dispatch in Swift
The best examples of error handling in Swift: 3 practical examples you’ll actually use
Explore More Swift Code Snippets
Discover more examples and insights in this category.
View All Swift Code Snippets