Introduction to Concurrency and Multithreading in Swift
Modern iOS and macOS applications increasingly require executing multiple tasks simultaneously. Whether it's loading data from the network, processing images, or complex mathematical calculations — proper thread management is essential. In Swift, concurrency and multithreading are implemented at the highest level, providing developers with powerful and safe tools.
In this article, we will cover three main approaches: Grand Central Dispatch (GCD), OperationQueue, and the modern async/await keyword, introduced in Swift 5.5. You will learn how to choose between them correctly and avoid common mistakes such as race conditions and deadlocks.
Grand Central Dispatch (GCD) — Classic Multithreading in Swift
Grand Central Dispatch (GCD) is a low-level Apple library that manages a thread pool. The developer does not need to create and destroy threads manually — GCD does this automatically, distributing tasks across available CPU cores.
Main GCD Queues
In GCD, there are two types of queues: serial and concurrent. Tasks in a serial queue are executed one after another, while in a concurrent queue they can be launched simultaneously.
// Creating a serial queuelet serialQueue = DispatchQueue(label: "com.example.serial")serialQueue.async { print("Task 1")}serialQueue.async { print("Task 2")}
// Creating a concurrent queuelet concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)concurrentQueue.async { print("Concurrent task A")}concurrentQueue.async { print("Concurrent task B")}Global Queues and QoS
GCD provides ready-made global queues with different priorities (Quality of Service). Use them when you don't need to create your own queue:
.userInteractive— for UI updates (highest priority).userInitiated— for tasks initiated by the user.utility— for long-running background operations.background— for tasks that are not time-critical
DispatchQueue.global(qos: .userInitiated).async { // Heavy work let result = performComplexCalculation() DispatchQueue.main.async { // Return to the main thread for UI updates self.updateUI(with: result) }}Important: Always update the UI only on the main thread (DispatchQueue.main). Ignoring this rule leads to unpredictable application crashes.
OperationQueue — Managing Task Dependencies
If GCD is a "Swiss Army knife", then OperationQueue is a full-fledged scheduler with the ability to cancel, prioritize, and create dependencies between operations. Each operation is an instance of the Operation class (or its subclass BlockOperation).
Creating and Executing Operations
let operationQueue = OperationQueue()operationQueue.maxConcurrentOperationCount = 2 // Limiting the number of concurrent operations
let operation1 = BlockOperation { print("Operation 1")}let operation2 = BlockOperation { print("Operation 2")}
// Adding a dependency: operation2 will start only after operation1 completesoperation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)Cancellation and Priorities
Unlike GCD, OperationQueue allows you to cancel a specific operation or the entire queue:
// Cancelling one operationoperation1.cancel()
// Cancelling the entire queueoperationQueue.cancelAllOperations()
// Setting priorityoperation1.