Swift 5.5 introduced the modern concurrency model that makes asynchronous programming easier. Write readable code with async/await syntax.
async/await
Simple async function
swift
// Define async function
func fetchUserData() async throws -> User {
// Async operation
let user = try await URLSession.shared.data(from: url)
return User(from: user)
}
// Call async function
Task {
do {
let user = try await fetchUserData()
print("User: \(user.name)")
} catch {
print("Error: \(error)")
}
}SwiftUI with async/await
swift
struct ProfileView: View {
@State private var user: User?
@State private var isLoading = false
var body: some View {
VStack {
if isLoading {
ProgressView("Loading...")
} else if let user = user {
Text(user.name)
.font(.title)
}
}
.task {
await loadUser()
}
}
func loadUser() async {
isLoading = true
defer { isLoading = false }
do {
user = try await fetchUserData()
} catch {
print("Error: \(error)")
}
}
}Structured Concurrency
Task Group
swift
func fetchMultipleImages() async throws -> [Image] {
try await withThrowingTaskGroup(of: Image.self) { group in
// Create parallel tasks
for id in 1...5 {
group.addTask {
try await fetchImage(id: id)
}
}
// Collect results
var images: [Image] = []
for try await image in group {
images.append(image)
}
return images
}
}Task Cancellation
swift
class ImageLoader: ObservableObject {
@Published var images: [Image] = []
private var loadTask: Task<Void, Never>?
func loadImages() {
// Cancel previous task
loadTask?.cancel()
// Start new task
loadTask = Task {
do {
let images = try await fetchMultipleImages()
// Check if task was cancelled
if !Task.isCancelled {
self.images = images
}
} catch {
if !Task.isCancelled {
print("Error: \(error)")
}
}
}
}
func cancelLoading() {
loadTask?.cancel()
}
}MainActor
swift
@MainActor
class ViewModel: ObservableObject {
@Published var data: String = ""
func fetchData() async {
// This function runs on main thread
let result = await fetchFromAPI()
self.data = result // UI update is safe
}
}
// Or
class ViewModel: ObservableObject {
@Published var data: String = ""
func fetchData() async {
let result = await fetchFromAPI()
// Run on main actor
await MainActor.run {
self.data = result
}
}
}Actor
Preventing Data Races
swift
actor Counter {
private var value = 0
func increment() -> Int {
value += 1
return value
}
func get() -> Int {
return value
}
}
let counter = Counter()
Task {
// Each call ensures actor serialization
print(await counter.increment())
print(await counter.get())
}Actor with Cache
swift
actor ImageCache {
private var storage: [String: Image] = [:]
func getImage(for key: String) -> Image? {
return storage[key]
}
func setImage(_ image: Image, for key: String) {
storage[key] = image
}
func clear() {
storage.removeAll()
}
}
// Usage
let cache = ImageCache()
Task {
// Thread-safe cache operations
await cache.setImage(image, for: "profile")
let cached = await cache.getImage(for: "profile")
}AsyncStream
swift
// Create AsyncStream
func generateNumbers() -> AsyncStream<Int> {
return AsyncStream { continuation in
var i = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
i += 1
continuation.yield(i)
if i >= 10 {
continuation.finish()
timer.invalidate()
}
}
}
}
// Use AsyncStream
Task {
for await number in generateNumbers() {
print("Number: \(number)")
}
}Continuation
swift
// Convert callback-based API to async/await
func fetchUser(id: String) async throws -> User {
try await withCheckedThrowingContinuation { continuation in
oldAPI.fetchUser(id) { result in
switch result {
case .success(let user):
continuation.resume(returning: user)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}Sendable Protocol
swift
// Sendable types for safe sharing in concurrency
struct User: Sendable {
let id: String
let name: String
}
// Actor is automatically Sendable
actor UserRepository {
private var users: [String: User] = [:]
func add(_ user: User) {
users[user.id] = user
}
}Best Practices
- 1Structured concurrency: Use Task and TaskGroup
- 2Cancellation: Check for cancellation in long operations
- 3MainActor: Use @MainActor for UI operations
- 4Actor: Use actor for shared mutable state
- 5Sendable: Use Sendable types at concurrency boundaries
Modern Swift concurrency makes asynchronous programming safer and more readable!