Swift Concurrency (async/await)

Modern asynchronous programming in Swift

⚡ What is Swift Concurrency?

Swift concurrency allows your app to do multiple things at once using async/await syntax. It makes asynchronous code easier to read and write, perfect for network requests and time-consuming tasks.


// Simple async function
func fetchUserName() async -> String {
    // Simulate network delay
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "John Doe"
}

// Using the async function
let name = await fetchUserName()
print(name) // "John Doe"
                                    

Key Concurrency Concepts

🔄

Async Functions

Functions that can be suspended

func loadData() async -> Data {
    // Asynchronous work here
    return Data()
}

Await Keyword

Wait for async operations to complete

let data = await loadData()
print("Data loaded!")
📋

Tasks

Units of asynchronous work

Task {
    let result = await doWork()
    print(result)
}
🚫

Error Handling

Handle errors in async code

do {
    let data = try await fetchData()
} catch {
    print("Error: \(error)")
}

🔹 Basic Async/Await

Create and use async functions:

// Async function that might throw an error
func fetchWeather(for city: String) async throws -> String {
    // Simulate API call
    try await Task.sleep(nanoseconds: 2_000_000_000)
    
    if city.isEmpty {
        throw WeatherError.invalidCity
    }
    
    return "Sunny, 75°F in \(city)"
}

enum WeatherError: Error {
    case invalidCity
}

// Using the async function
Task {
    do {
        let weather = try await fetchWeather(for: "New York")
        print(weather) // "Sunny, 75°F in New York"
    } catch {
        print("Failed to get weather: \(error)")
    }
}

🔹 Parallel Execution

Run multiple async operations simultaneously:

// Sequential execution (slow)
func fetchDataSequentially() async {
    let user = await fetchUser()
    let posts = await fetchPosts()
    let comments = await fetchComments()
    
    print("All data loaded sequentially")
}

// Parallel execution (fast)
func fetchDataInParallel() async {
    async let user = fetchUser()
    async let posts = fetchPosts()
    async let comments = fetchComments()
    
    let (userData, postsData, commentsData) = await (user, posts, comments)
    print("All data loaded in parallel")
}

// Using Task.group for dynamic parallel work
func fetchMultipleUsers(ids: [String]) async -> [User] {
    await withTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                await fetchUser(id: id)
            }
        }
        
        var users: [User] = []
        for await user in group {
            users.append(user)
        }
        return users
    }
}

🔹 Task Management

Create and manage tasks effectively:

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

// Getting the result
let result = await task.value

// Cancelling a task
task.cancel()

// Checking if cancelled
Task {
    for i in 1...100 {
        if Task.isCancelled {
            print("Task was cancelled")
            return
        }
        await doWork(step: i)
    }
}

// Detached task (doesn't inherit context)
Task.detached {
    await backgroundWork()
}

🔹 MainActor and UI Updates

Ensure UI updates happen on the main thread:

import SwiftUI

class ViewModel: ObservableObject {
    @Published var data: String = ""
    @Published var isLoading = false
    
    @MainActor
    func loadData() async {
        isLoading = true
        
        // This runs on background
        let result = await fetchDataFromAPI()
        
        // UI updates automatically on main thread
        data = result
        isLoading = false
    }
}

// In SwiftUI View
struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            if viewModel.isLoading {
                ProgressView("Loading...")
            } else {
                Text(viewModel.data)
            }
        }
        .task {
            await viewModel.loadData()
        }
    }
}

🔹 Best Practices

Follow these guidelines for effective async code:

✅ Do:

  • Use async/await instead of completion handlers
  • Use async let for parallel operations
  • Handle cancellation in long-running tasks
  • Use @MainActor for UI-related code
  • Prefer structured concurrency over unstructured tasks

❌ Don't:

  • Block threads with synchronous operations
  • Create too many concurrent tasks without limits
  • Ignore task cancellation in loops
  • Update UI from background threads

🧠 Test Your Knowledge

What keyword do you use to wait for an async function to complete?