Introduction
Grand Central Dispatch (GCD) is a powerful API in Swift that enables efficient, concurrent task execution. It helps you manage background tasks, prevent UI freezes, and optimize app performance by distributing tasks across multiple threads.
In this guide, we’ll cover everything from basic concepts to advanced usage, including why GCD is needed, how to use it, and best practices.
Why Do We Need GCD?
iOS applications run on a single main thread by default. If we perform heavy operations (network calls, file I/O, database processing, etc.) on this main thread, it blocks the UI, making the app unresponsive.
GCD allows us to:
Execute tasks asynchronously without blocking the UI
Utilize multiple CPU cores efficiently
Prioritize tasks for better performance
Improve responsiveness by running operations in the background
Understanding GCD Basics
1. Dispatch Queues
A dispatch queue is a way to manage tasks in GCD. It determines where and how tasks are executed.
Types of Dispatch Queues
- Main Queue (<span class="text-color-code">DispatchQueue.main</span>) – Executes tasks on the main thread (UI updates).
- Global Queues (<span class="text-color-code">DispatchQueue.global(qos:)</span>) – Executes tasks on background threads.
- Custom Serial/Concurrent Queues — Created manually for fine-tuned concurrency control.
Dispatch Queue Types in Detail
Queue Type Execution Type Example Use Case Main Queue Serial (Runs on main thread) UI updates Global Queue Concurrent Background tasks (network requests, file I/O) Custom Serial Queue Serial Executing tasks in sequence Custom Concurrent Queue Concurrent Running multiple tasks in parallel
2. Dispatch Queue Execution Methods
You can execute tasks synchronously (<span class="text-color-code">sync</span>) or asynchronously (<span class="text-color-code">async</span>).
Synchronous Execution (<span class="text-color-code">sync</span>
)
- Blocks the current thread until the task is complete.
- Not recommended for long-running tasks.
DispatchQueue.global().sync {
print("Executing a synchronous task")
}
Asynchronous Execution (<span class="text-color-code">async</span>
)
- Runs the task in the background without blocking the current thread.
- Ideal for UI performance.
DispatchQueue.global().async {
print("Executing an asynchronous task")
}
Warning: Never call <span class="text-color-code">sync</span>
on the main thread, as it can cause a deadlock!
3. DispatchQueue Priorities (QoS)
Quality of Service (QoS) determines the priority of tasks.
QoS Type Priority Use Case <span class="text-color-code">.userInteractive</span> Highest UI animations, event handling <span class="text-color-code">.userInitiated</span> High Short tasks initiated by the user <span class="text-color-code">.default</span> Medium General tasks <span class="text-color-code">.utility</span> Low Long-running background tasks (e.g., downloads) <span class="text-color-code">.background</span> Lowest Data syncing, analytics
Example: Running a background task with low priority
DispatchQueue.global(qos: .background).async {
print("Background task running")
}
4. Creating Custom Dispatch Queues
You can create your own serial or concurrent queues.
Creating a Serial Queue
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
serialQueue.async {
print("Task 1")
}
serialQueue.async {
print("Task 2")
}
Tasks execute one after another in the order they were added.
Creating a Concurrent Queue
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
concurrentQueue.async {
print("Task A")
}
concurrentQueue.async {
print("Task B")
}
Tasks execute simultaneously in any order.
5. DispatchGroup — Synchronizing Multiple Tasks
DispatchGroup allows multiple asynchronous tasks to complete before executing a final task.
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
print("Task 1")
group.leave()
}
group.enter()
DispatchQueue.global().async {
print("Task 2")
group.leave()
}
group.notify(queue: .main) {
print("All tasks completed")
}
Use Case: Wait for multiple API calls to finish before updating the UI.
6. DispatchWorkItem — Cancellable & Repeatable Tasks
A <span class="text-color-code">DispatchWorkItem</span>
is a block of code that can be cancelled or re-executed.
let workItem = DispatchWorkItem {
print("Executing work item")
}
DispatchQueue.global().async(execute: workItem)
// Cancel it before execution
workItem.cancel()
Use Case: Cancel an ongoing task if it’s no longer needed.
7. DispatchSemaphore — Controlling Concurrency
A <span class="text-color-code">DispatchSemaphore</span> controls how many tasks execute concurrently.
let semaphore = DispatchSemaphore(value: 2) // Max 2 concurrent tasks
DispatchQueue.global().async {
semaphore.wait()
print("Task 1")
sleep(2) // Simulating work
semaphore.signal()
}
DispatchQueue.global().async {
semaphore.wait()
print("Task 2")
sleep(2)
semaphore.signal()
}
DispatchQueue.global().async {
semaphore.wait()
print("Task 3")
sleep(2)
semaphore.signal()
}
Use Case: Limit API requests to avoid overloading a server.
8. DispatchBarrier — Synchronizing Read/Write Tasks
A <span class="text-color-code">DispatchBarrier</span> ensures thread-safe modifications to shared resources.
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
var sharedResource = 0
// Reading (Multiple Threads)
concurrentQueue.async {
print("Read \(sharedResource)")
}
// Writing (Single Thread with Barrier)
concurrentQueue.async(flags: .barrier) {
sharedResource += 1
print("Write \(sharedResource)")
}
Use Case: Updating a shared database or cache.
9. DispatchSource — Monitoring System Events
<span class="text-color-code">DispatchSource</span> listens for system events (e.g., file changes, timers).
Example: Timer-based DispatchSource
let timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: .now(), repeating: 5.0) // Fires every 5 seconds
timer.setEventHandler {
print("Timer fired!")
}
timer.resume() // Start the timer
Use Case: Monitor file changes, network availability.
Best Practices for Using GCD
- Use <span class="text-color-code">async</span> for UI updates to avoid blocking the main thread.
- Choose the right QoS for better performance.
- Use <span class="text-color-code">DispatchGroup</span> when waiting for multiple tasks.
- Avoid race conditions by using <span class="text-color-code">DispatchBarrier</span> for shared resources.
- Limit concurrent tasks using <span class="text-color-code">DispatchSemaphore</span> when needed.
Conclusion
GCD is a powerful tool in Swift that enables efficient multitasking, optimizing app responsiveness and resource usage. By mastering GCD, you can build high-performance iOS applications that run smoothly even under heavy workloads.
Key Takeaways:
- Use dispatch queues (<span class="text-color-code">main</span>, <span class="text-color-code">global</span>, custom serial/concurrent).
- Prefer async over sync to prevent blocking.
- Leverage QoS to prioritize tasks efficiently.
- Use DispatchGroup, Semaphore, and Barrier for task management.
Start implementing GCD in your iOS apps today and take your concurrency skills to the next level!
Send us a quick query
Recommended Posts

Industry 4.0: Into the Future

Analyzing Hybrid Mobile App Development: Pure gold for early startups?
