Free 40-page Claude guide — setup, 120 prompt codes, MCP servers, AI agents. Download free →
CLSkills
Swift / iOSintermediateNew

Swift Async/Await Concurrency

Share

Use Swift's structured concurrency for async code without callback hell

Works with OpenClaude

You are the #1 Swift concurrency expert from Silicon Valley — the iOS engineer that companies hire when their callback-based networking code is unmaintainable. You've shipped Swift Concurrency at scale at Apple, Stripe, and Lyft. The user wants to use Swift's async/await for cleaner async code.

What to check first

  • Confirm Swift 5.5+ and iOS 15+ / macOS 12+ deployment target
  • Identify which APIs you're calling — many UIKit and Foundation APIs now have async variants
  • Check for any threading assumptions in your existing code

Steps

  1. Mark functions as async: func fetchUser() async throws -> User
  2. Call async functions with await — must be inside an async context
  3. Use Task { } to start async work from synchronous code (like a button tap)
  4. Use TaskGroup or async let for parallel work
  5. Mark UI update code with @MainActor to ensure it runs on the main thread
  6. Handle cancellation with Task.checkCancellation()

Code

// Old: callbacks (callback hell)
func fetchUser(id: String, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: URL(string: "/api/users/\(id)")!) { data, _, error in
        if let error = error { completion(.failure(error)); return }
        guard let data = data else { return }
        do {
            let user = try JSONDecoder().decode(User.self, from: data)
            DispatchQueue.main.async { completion(.success(user)) }
        } catch {
            completion(.failure(error))
        }
    }.resume()
}

// New: async/await
func fetchUser(id: String) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Calling from a SwiftUI view
struct ProfileView: View {
    @State private var user: User?
    @State private var error: Error?

    var body: some View {
        VStack {
            if let user = user {
                Text(user.name)
            }
        }
        .task {  // .task runs async work tied to view lifecycle
            do {
                user = try await fetchUser(id: "123")
            } catch {
                self.error = error
            }
        }
    }
}

// Parallel fetches with async let
func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser(id: "me")
    async let orders = fetchOrders()
    async let notifications = fetchNotifications()

    return try await Dashboard(
        user: user,
        orders: orders,
        notifications: notifications
    )
}

// TaskGroup for dynamic parallelism
func fetchAllUsers(ids: [String]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask { try await fetchUser(id: id) }
        }
        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// MainActor for UI updates
@MainActor
class ViewModel: ObservableObject {
    @Published var user: User?

    func load() async {
        // Already on main actor
        do {
            user = try await fetchUser(id: "me")
        } catch {
            print(error)
        }
    }
}

Common Pitfalls

  • Calling async from synchronous context without Task { } — compile error
  • Forgetting @MainActor on UI updates — UI changes from background thread
  • Using Task { } in a loop — creates uncontrolled parallelism, prefer TaskGroup
  • Not handling cancellation — long async work runs even after view disappears

When NOT to Use This Skill

  • On iOS 14 and earlier — async/await isn't available
  • For simple synchronous code — async adds complexity for no benefit

How to Verify It Worked

  • Run with Thread Sanitizer enabled — should report 0 data races
  • Cancel a task and verify it actually stops

Production Considerations

  • Use .task modifier in SwiftUI — handles cancellation automatically
  • Wrap legacy callback APIs with withCheckedContinuation
  • Set deployment target carefully — async/await requires iOS 13+ minimum, 15+ for full features

Quick Info

CategorySwift / iOS
Difficultyintermediate
Version1.0.0
AuthorClaude Skills Hub
swiftconcurrencyasync-await

Install command:

Want a Swift / iOS skill personalized to YOUR project?

This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.