Task Queues in Swift: from GCD to Swift Concurrency
In iOS and macOS development, managing parallel code execution is one of the key tasks. Task queues allow you to efficiently distribute load across threads without blocking the user interface. In this guide, we will cover three main approaches: classic Grand Central Dispatch (GCD), the OperationQueue abstraction, and the modern Swift Concurrency mechanism (async/await).
1. Grand Central Dispatch (GCD) — the foundation of parallelism
GCD is a low-level C library integrated into Swift. It manages a thread pool and provides two types of queues:
- Serial — tasks execute one after another in the order they are added.
- Concurrent — tasks start simultaneously, completion order is not guaranteed.
Example of creating and using a queue:
import Foundation
// Create a serial queuelet serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async { print("Task 1 started") sleep(1) print("Task 1 completed")}
serialQueue.async { print("Task 2 started") sleep(1) print("Task 2 completed")}
// Concurrent queuelet concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
concurrentQueue.async { print("Concurrent task A")}
concurrentQueue.async { print("Concurrent task B")}Key GCD features:
DispatchQueue.main— the main queue (UI thread).DispatchQueue.global(qos: .userInitiated)— global queues with priority (QoS).dispatchGroup— synchronization of multiple tasks.dispatchSemaphore— controlling access to resources.
2. OperationQueue — object-oriented abstraction
OperationQueue is built on top of GCD but provides a higher-level API. Main advantages:
- Managing dependencies between operations.
- Canceling tasks (cancel).
- State monitoring (isFinished, isExecuting).
- Maximum number of concurrent operations.
Example using BlockOperation:
import Foundation
let operationQueue = OperationQueue()operationQueue.maxConcurrentOperationCount = 2
let operation1 = BlockOperation { print("Operation 1: loading data") sleep(2) print("Operation 1 completed")}
let operation2 = BlockOperation { print("Operation 2: processing") sleep(1) print("Operation 2 completed")}
// Set dependency: operation 2 will start only after operation 1 completesoperation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)
// Cancel operationoperation1.cancel()When to use OperationQueue:
- Complex task chains with dependencies.
- Need for fine-grained control (pause, cancel).
- Working with KVO (Key-Value Observing) for monitoring.
3. Swift Concurrency — modern approach (iOS 13+, macOS 10.15+)
Starting with Swift 5.5, Apple introduced built-in support for asynchronous programming through async/await, Task, and TaskGroup. This is a safer and more readable way to work with concurrency.
Basic example:
import Foundation
func fetchUserData() async -> String { // Simulate network request try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) return "User data loaded"}
func processData()