Async Programming in Swift: A Complete Practical Guide

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

Async Programming in Swift: A Complete Practical Guide



Async programming is one of the key concepts in modern iOS and macOS development. In Swift, starting from version 5.5, a powerful Structured Concurrency system appeared, making working with asynchrony simple and safe. In this article, we will break down the main tools: async/await, Task, Actor and Sendable, and also show their application with real examples.



1. Basics of async/await: How to Stop Writing Closures and Start Living



Before the advent of async/await, async code in Swift was written using closures and delegates, which often led to "callback hell." The modern approach allows you to write async code as linearly as synchronous code.



You can declare an async function using the async keyword. Calling such a function requires a context that supports asynchrony and the await keyword.



// Example of an async data loading functionfunc fetchUserData(userID: String) async throws -> User {    let url = URL(string: "https://api.example.com/users/\\(userID)")!    let (data, response) = try await URLSession.shared.data(from: url)        guard let httpResponse = response as? HTTPURLResponse,          httpResponse.statusCode == 200 else {        throw URLError(.badServerResponse)    }        let user = try JSONDecoder().decode(User.self, from: data)    return user}

// UsageTask { do { let user = try await fetchUserData(userID: "123") print("User name: \\(user.name)") } catch { print("Error: \\(error)") }}


Key Features:

  • async — marker of an async function
  • await — suspension point of execution
  • A function can be both async and throws simultaneously
  • The thread is not blocked — during the wait, it can perform other work


2. Task and TaskGroup: Managing Concurrent Tasks



Task is the basic unit of async work. It creates a new async task that runs on the global thread pool. TaskGroup allows you to launch multiple tasks in parallel and collect their results.



Creating and Cancelling a Task



// Creating a tasklet task = Task {    let result = await performLongOperation()    return result}

// Getting the resultlet value = await task.value

// Cancelling the tasktask.cancel()

// Checking for cancellation inside the taskfunc performLongOperation() async -> String { // Check if the task is cancelled guard !Task.isCancelled else { return "Cancelled" } // Emulate long work try? await Task.sleep(nanoseconds: 2_000_000_000) return "Completed"}


Parallel Processing with TaskGroup



// Loading multiple images in parallelfunc loadImages(urls: [URL]) async throws -> [UIImage] {    return try await withThrowingTaskGroup(of: UIImage.self) { group in        var images: [UIImage] = []        images.reserveCapacity(urls.count)                for url in urls {            group.addTask {                let (data, _) = try await URLSession.shared.data(from: url)                guard let image = UIImage(data: data) else {                    throw URLError(.cannotDecodeContentData)                }                return image            }        }                // Collect results as they become ready        for try await image in group {            images.append(image)        }                return images    }}


Important: TaskGroup automatically manages the lifecycle of child tasks

Blogs

Book Recommendations