Concurrency and Multithreading in Swift: Advanced Techniques

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Concurrency and Multithreading in Swift: Advanced Level

Concurrency is one of the most complex and important topics in iOS/macOS development. At an advanced level, Swift offers powerful tools: from the classic Grand Central Dispatch (GCD) to the modern async/await model and the strict Actors system. In this article, we will break down how to efficiently manage threads, avoid data races, and write thread-safe code.



1. Grand Central Dispatch (GCD): Working with Queues and Groups

GCD remains the foundation of multithreading in Swift. However, at an advanced level, it's important to understand not only the difference between main and global(), but also how to manage task dependencies.

Creating Custom Queues

let serialQueue = DispatchQueue(label: "com.pythonlib.serial")let concurrentQueue = DispatchQueue(label: "com.pythonlib.concurrent", attributes: .concurrent)

serialQueue.async { print("Task 1 in serial queue")}concurrentQueue.async { print("Task 2 in concurrent queue")}

DispatchGroup: Synchronizing Multiple Tasks

let group = DispatchGroup()let queue = DispatchQueue.global()

queue.async(group: group) { sleep(1) print("Loading data 1")}queue.async(group: group) { sleep(2) print("Loading data 2")}

group.notify(queue: DispatchQueue.main) { print("All loads complete, updating UI")}

Use DispatchGroup when you need to wait for the completion of several parallel operations (e.g., loading images or parsing JSON).



2. Modern async/await Model and Task

Starting with Swift 5.5, the language gained native support for asynchrony through the async and await keywords. This simplifies code readability and eliminates the "pyramid of doom".

Declaring and Calling an Async Function

func fetchUserData() async -> String {    // Simulating a network request    try? await Task.sleep(nanoseconds: 2_000_000_000)    return "User data loaded"}

Task { let result = await fetchUserData() print(result) // Prints "User data loaded"}

Parallel Calls with async let

func loadImage() async -> Data { return Data() }func loadMetadata() async -> String { return "meta" }

Task { async let image = loadImage() async let metadata = loadMetadata() let result = try await (image, metadata) print("Image: \\(result.0), metadata: \\(result.1)")}

async let allows you to launch multiple async tasks in parallel and wait for all of them. This is more efficient than sequential calls.



3. Actors: Thread Safety at the Language Level

Actors are a new mechanism for protecting against data races. They guarantee that access to their state will be synchronized automatically.

Defining and Using an Actor

actor BankAccount {    private var balance: Double = 0.0        func deposit(amount: Double) {        balance += amount        print("Balance after deposit: \\(balance)")    }        func withdraw(amount: Double) -> Bool {        guard balance >= amount else { return false }        balance -= amount        return true    }        func getBalance() -> Double { balance }}

Task { let account = BankAccount() await account.deposit(amount: 100) let success = await account.withdraw(amount: 50) print("Withdrawal successful: \\(success)")}

All actor methods are isolated by default — they can only be called via await. This prevents accidental data modification from multiple threads.

Nonisolated Methods

actor Logger {    nonisolated func log(message: String) {        print("Log: \\(message)")\                

Blogs

Book Recommendations