Swift Concurrency Basics

Swift’s modern concurrency model makes asynchronous programming safer and easier to reason about. Let’s explore the basics!

Async/Await

The foundation of Swift concurrency is async and 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.struct, from: data)
}

Task Groups

For parallel operations, use task groups:

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
    }
}

Actors

Actors provide data isolation:

actor UserCache {
    private var cache: [String: User] = [:]

    func get(id: String) async -> User? {
        return cache[id]
    }

    func set(user: User) async {
        cache[user.id] = user
    }
}

Best Practices

  1. Use async let for independent operations

  2. Prefer structured concurrency over raw Task

  3. Use actors for shared mutable state

  4. Mark functions async when they perform asynchronous work

Conclusion

Swift concurrency makes asynchronous code safer and more maintainable. Start adopting it in your projects today!