Swift / iOSintermediateNew
Use Swift's structured concurrency for async code without callback hell
✓Works with OpenClaudeYou 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
- Mark functions as async: func fetchUser() async throws -> User
- Call async functions with await — must be inside an async context
- Use Task { } to start async work from synchronous code (like a button tap)
- Use TaskGroup or async let for parallel work
- Mark UI update code with @MainActor to ensure it runs on the main thread
- 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
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.