Lesson 94Advanced Topics

Async/Await Basics

Modern Concurrency

Swift's async/await syntax makes asynchronous code look like synchronous code. No more callback pyramids or completion handlers!

Keywords

async

Marks function as asynchronous

func fetch() async -> Data

await

Pauses until result ready

let data = await fetch()

async let

Start parallel tasks

async let a = fetch()

Task { }

Bridge sync → async

Task { await load() }

Sequential vs Parallel

Sequential

let a = await fetchA()
let b = await fetchB()
// Total: timeA + timeB

Parallel

async let a = fetchA()
async let b = fetchB()
await (a, b)
// Total: max(timeA, timeB)

Error Handling

func fetch() async throws -> Data

do {
    let data = try await fetch()
} catch { ... }

Task Cancellation

  • Task.checkCancellation() - Throws if cancelled
  • Task.isCancelled - Check manually
  • task.cancel() - Request cancellation
main.swift
// ASYNC/AWAIT BASICS
// Modern Swift concurrency - cleaner async code

// ===== ASYNC FUNCTIONS =====
// Mark functions that can pause with 'async'
func fetchUserData() async -> String {
    // Simulate network delay
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "User data loaded"
}

// Call async functions with 'await'
func loadData() async {
    let data = await fetchUserData()
    print(data)
}

// ===== ASYNC THROWS =====
enum NetworkError: Error {
    case badConnection
    case notFound
}

func fetchUser(id: Int) async throws -> String {
    try await Task.sleep(nanoseconds: 500_000_000)

    if id <= 0 {
        throw NetworkError.notFound
    }
    return "User \(id)"
}

// Call with try await
func getUser() async {
    do {
        let user = try await fetchUser(id: 1)
        print("Got: \(user)")
    } catch {
        print("Error: \(error)")
    }
}

// ===== SEQUENTIAL VS PARALLEL =====

// Sequential - one after another
func loadSequential() async {
    let user = await fetchUserData()
    let posts = await fetchPosts()  // Waits for user first
    print("\(user), \(posts)")
}

func fetchPosts() async -> String {
    try? await Task.sleep(nanoseconds: 500_000_000)
    return "Posts loaded"
}

// Parallel - simultaneously
func loadParallel() async {
    async let user = fetchUserData()
    async let posts = fetchPosts()

    // Both start immediately, await collects results
    let results = await (user, posts)
    print("\(results.0), \(results.1)")
}

// ===== TASK =====
// Run async code from sync context
func startLoading() {
    Task {
        await loadData()
    }
}

// Task with priority
func startPriorityTask() {
    Task(priority: .high) {
        await loadData()
    }
}

// ===== TASK CANCELLATION =====
func cancellableTask() async throws -> String {
    for i in 1...10 {
        // Check if cancelled
        try Task.checkCancellation()

        // Or check manually
        if Task.isCancelled {
            return "Cancelled"
        }

        try await Task.sleep(nanoseconds: 100_000_000)
        print("Step \(i)")
    }
    return "Completed"
}

// Cancel a task
var task: Task<String, Error>?

func startTask() {
    task = Task {
        try await cancellableTask()
    }
}

func cancelTask() {
    task?.cancel()
}

// ===== ASYNC SEQUENCES =====
func countdown() async {
    for await number in AsyncStream<Int> { continuation in
        for i in (1...5).reversed() {
            continuation.yield(i)
        }
        continuation.finish()
    } {
        print(number)
    }
}

// ===== CONVERTING COMPLETION HANDLERS =====
// Old callback-based API
func fetchDataOld(completion: @escaping (String) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        completion("Data")
    }
}

// Wrap with async/await
func fetchDataAsync() async -> String {
    await withCheckedContinuation { continuation in
        fetchDataOld { data in
            continuation.resume(returning: data)
        }
    }
}

// For throwing version
func fetchDataAsyncThrows() async throws -> String {
    try await withCheckedThrowingContinuation { continuation in
        // continuation.resume(throwing: error)
        // continuation.resume(returning: value)
        continuation.resume(returning: "Data")
    }
}

// ===== @MAIN AND ASYNC =====
// Can use async in main
@main
struct MyApp {
    static func main() async {
        await loadData()
    }
}

Try It Yourself!

Create an async function that fetches weather data for 3 cities in parallel and returns the combined results!