Introduction to Task Queues in Swift
Modern mobile and desktop applications need to be fast and responsive. If you perform heavy computations or load data from the network directly on the main thread, your application will freeze, and the user will simply close it. The solution is task queues.
In Swift (iOS/macOS), asynchronous management is built on three main technologies: Grand Central Dispatch (GCD), Operation Queue, and the modern async/await syntax. In this article, we will break down each of them from scratch, show code examples, and explain when to use which.
1. Grand Central Dispatch (GCD) and DispatchQueue
GCD is Apple's low-level library for managing parallel code execution. The main element is DispatchQueue. The queue executes tasks (closures) in the order they are added.
Queue Types
- Main Queue — a serial queue on the main thread. Used for updating the UI.
- Global Queues — concurrent queues with different priorities (QoS:
.userInteractive,.userInitiated,.utility,.background). - Custom Queues — your own queues (serial or concurrent).
Example: Performing a Task in the Background
import Foundation
// Asynchronously on a background queueDispatchQueue.global(qos: .background).async { // Heavy work (loading, data processing) let result = performHeavyTask() // Return the result to the main thread DispatchQueue.main.async { updateUI(with: result) }}
func performHeavyTask() -> String { Thread.sleep(forTimeInterval: 2) // simulate work return "Done!"}
func updateUI(with text: String) { print("UI updated: \\(text)")}Note: .async does not block the calling thread. If you need to wait for the result, use .sync (but never call .sync from the main thread — you will get a deadlock).
Example: Serial Queue
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async { print("Task 1")}serialQueue.async { print("Task 2")}// Output: Task 1, then Task 22. Operation Queue — A More Flexible Approach
OperationQueue is an abstraction over GCD, providing an object-oriented interface. You create an Operation (or BlockOperation), configure dependencies, and add it to the queue.
Advantages over GCD
- Task cancellation (
cancel()) - Dependencies between tasks
- State monitoring (isReady, isExecuting, isFinished)
- Maximum number of concurrent operations
Example: Basic Usage
let operationQueue = OperationQueue()operationQueue.maxConcurrentOperationCount = 2 // no more than 2 tasks at a time
let operation1 = BlockOperation { print("Loading data...") Thread.sleep(forTimeInterval: 1)}
let operation2 = BlockOperation { print("Processing data...") Thread.sleep(forTimeInterval: 1)}
// operation2 will start only after operation1 completesoperation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)Example: Cancelling an Operation
let cancelableOp = BlockOperation { if !cancelableOp.isCancelled { print("Performing important work") }}
operationQueue.addOperation(cancelableOp)
// Cancel after some timeDispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { cancelableOp.cancel() print("Operation cancelled")}