How to Implement an Efficient Repeating Timer in Swift Using DispatchSourceTimer

Use DispatchSource.makeTimerSource() to create a DispatchSourceTimer on a dedicated DispatchQueue for nanosecond‑precision, low‑overhead periodic execution that avoids RunLoop jitter and UI thread contention.

Efficient repeating timers in Swift require careful selection of underlying timing mechanisms to balance precision, resource usage, and thread safety. While Foundation's Timer class is familiar to many developers, the apple/swift repository demonstrates that Grand Central Dispatch (GCD) provides a more performant alternative for background tasks. According to benchmark utilities and standard library tests in the Swift codebase, DispatchSourceTimer delivers superior performance through kernel‑driven scheduling and direct access to high‑resolution system clocks.

Why DispatchSourceTimer is the Most Efficient Approach

When comparing timing mechanisms, DispatchSourceTimer outperforms Foundation.Timer and CADisplayLink across critical dimensions including precision, thread affinity, and power efficiency.

Nanosecond Precision via Mach Timebase

DispatchSourceTimer achieves nanosecond‑level accuracy by leveraging mach_absolute_time on Apple platforms and clock_gettime on Linux. This contrasts with Foundation Timer, which operates at millisecond precision and suffers from RunLoop jitter.

In benchmark/utils/DriverUtils.swift (lines 320‑558), the Swift repository implements a custom Timer utility that reveals this low‑level architecture:

final class Timer {
#if os(Linux)
  typealias TimeT = timespec
  func getTime() -> TimeT { /* uses clock_gettime */ }
  func diffTimeInNanoSeconds(...) -> UInt64 { /* normalizes nanoseconds */ }
#else
  typealias TimeT = UInt64
  var info = mach_timebase_info_data_t()
  init() { mach_timebase_info(&info) }
  func getTime() -> TimeT { return mach_absolute_time() }
  func diffTimeInNanoSeconds(...) -> UInt64 { /* scales via timebase info */ }
#endif
}

This implementation mirrors the mechanism that DispatchSourceTimer utilizes internally, bypassing the higher‑level RunLoop abstraction for direct kernel access.

Thread Safety and Queue Targeting

Unlike Foundation.Timer, which requires attachment to a specific RunLoop and often forces execution on the main thread, DispatchSourceTimer can target any DispatchQueue. This eliminates UI blocking by scheduling work on background queues.

The test suite in test/stdlib/DispatchTypes.swift (line 42) demonstrates the canonical instantiation pattern:

let source = DispatchSource.makeTimerSource()

Low Resource Overhead

DispatchSourceTimer operates as a fully kernel‑driven mechanism that wakes only when the interval expires. This design minimizes CPU wake‑ups and power consumption compared to Foundation.Timer, which requires RunLoop bookkeeping and can unnecessarily wake the UI thread.

Implementing Repeating Timers in Production Code

The following patterns demonstrate how to implement efficient repeating timers using GCD, as validated by the Swift repository's sanitizer tests and benchmark harness.

Basic Background Timer

Create a timer on a dedicated utility queue to prevent main thread contention:

import Dispatch

let timerQueue = DispatchQueue(label: "com.example.timer", qos: .utility)
let timer = DispatchSource.makeTimerSource(queue: timerQueue)

// Schedule first fire after 500ms, then repeat every 2 seconds
timer.schedule(deadline: .now() + .milliseconds(500),
               repeating: .seconds(2),
               leeway: .milliseconds(100))

timer.setEventHandler {
    print("Executing periodic task")
}

// Activation required after configuration
timer.resume()

The leeway parameter allows the system to optimize power by shifting the deadline slightly without affecting logical correctness.

Dynamically Reconfigurable Timer

For scenarios requiring runtime interval changes, encapsulate the timer in a class that exposes fine‑grained control:

class ReconfigurableTimer {
    private let source: DispatchSourceTimer
    private let queue: DispatchQueue

    init(queue: DispatchQueue = .global()) {
        self.queue = queue
        self.source = DispatchSource.makeTimerSource(queue: queue)
        self.source.setEventHandler(handler: work)
        self.source.resume()
    }

    private func work() {
        // Periodic task execution
    }

    func schedule(after interval: DispatchTimeInterval,
                  repeatEvery repeatInterval: DispatchTimeInterval) {
        source.schedule(deadline: .now() + interval,
                       repeating: repeatInterval)
    }

    func cancel() {
        source.cancel()
    }
}

This pattern appears in concurrent testing scenarios such as test/Sanitizers/tsan/norace-block-release.swift (lines 25‑27), where timer lifecycle management must be explicit and thread‑safe.

High‑Frequency Sub‑Millisecond Timer

For audio processing or physics simulation requiring sub‑millisecond resolution:

let hiResTimer = DispatchSource.makeTimerSource()
hiResTimer.schedule(deadline: .now(),
                    repeating: .nanoseconds(500_000), // 0.5ms
                    leeway: .nanoseconds(100_000))
hiResTimer.setEventHandler { 
    // Real‑time critical work
}
hiResTimer.resume()

Source Code References in the Swift Repository

The apple/swift repository provides authoritative implementation examples that demonstrate DispatchSourceTimer usage in production environments:

Summary

  • DispatchSourceTimer provides the most efficient repeating timer implementation in Swift, utilizing kernel‑driven scheduling and nanosecond‑precision clocks.
  • Target dedicated DispatchQueue instances rather than the main RunLoop to eliminate UI thread contention.
  • Use schedule(deadline:repeating:leeway:) to configure intervals, specifying leeway for power optimization.
  • Call resume() only after configuring handlers and schedules to prevent premature firing.
  • Invoke cancel() for clean shutdown, optionally setting a cancellation handler for resource cleanup.

Frequently Asked Questions

What is the difference between DispatchSourceTimer and Foundation Timer?

DispatchSourceTimer operates at the kernel level using mach_absolute_time or clock_gettime, providing nanosecond precision and queue‑based execution without RunLoop overhead. Foundation.Timer runs on a RunLoop, offers only millisecond precision, and typically requires the main thread, causing potential UI blocking and higher power consumption due to frequent wake‑ups.

How do I cancel a DispatchSourceTimer correctly?

Call timer.cancel() to stop future invocations. If you need to perform cleanup after cancellation, set a cancellation handler using setCancelHandler before calling cancel. The cancellation handler executes on the timer's target queue once the timer is fully stopped, as demonstrated in the ThreadSanitizer tests within the Swift repository.

Can I use DispatchSourceTimer on the main thread for UI updates?

Yes, by initializing the timer with DispatchQueue.main as the target queue: DispatchSource.makeTimerSource(queue: DispatchQueue.main). However, for UI updates, CADisplayLink is often more appropriate as it synchronizes with the display refresh rate. Reserve DispatchSourceTimer on the main queue only when you need precise timing control for UI‑related tasks without display synchronization.

What is the maximum precision available with DispatchSourceTimer?

On Apple platforms, DispatchSourceTimer utilizes the Mach absolute time facility (mach_absolute_time), providing nanosecond‑level resolution. On Linux, it uses clock_gettime with CLOCK_MONOTONIC. You can specify intervals in nanoseconds using .nanoseconds(), though actual precision depends on hardware capabilities and system load. The benchmark utilities in benchmark/utils/DriverUtils.swift demonstrate how to calculate nanosecond differences using the Mach timebase info conversion factor.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →