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