Swift’s structured concurrency eliminates callback pyramids and race conditions through the type system. async/await suspends without blocking threads. actor types serialize access to mutable state — the compiler enforces isolation. TaskGroup launches child tasks with automatic cancellation propagation. AsyncStream bridges delegate callbacks and notification center to async iteration. Swift 6’s strict concurrency checking makes data races compile-time errors. @MainActor isolates UI updates to the main thread with compile-time enforcement. Claude Code generates Swift async functions, actor types, TaskGroup patterns, AsyncStream bridges, and the SwiftUI async data loading patterns for production iOS and macOS apps.
CLAUDE.md for Swift Concurrency
## Swift Concurrency Stack
- Swift >= 5.10 (strict concurrency available), targeting Swift 6 semantics
- Enable: SWIFT_STRICT_CONCURRENCY = complete in Xcode build settings
- async/await: use for all I/O, network, and disk operations
- Actor: use for mutable shared state — never use DispatchQueue.main.async
- MainActor: @MainActor for all view models and UI-observable state
- Task: structured (TaskGroup) preferred over unstructured (Task.detached)
- Sendable: all types crossing actor boundaries must be Sendable
Async/Await Fundamentals
// Services/OrderService.swift — async service layer
import Foundation
struct Order: Codable, Sendable, Identifiable {
let id: String
let customerId: String
let amount: Int
let status: OrderStatus
let createdAt: Date
}
enum OrderStatus: String, Codable, Sendable {
case pending, processing, shipped, delivered, cancelled
}
enum OrderError: Error {
case notFound
case networkError(Error)
case invalidResponse
case unauthorized
}
actor OrderService {
private let baseURL: URL
private let session: URLSession
private var cache: [String: Order] = [:]
init(baseURL: URL, session: URLSession = .shared) {
self.baseURL = baseURL
self.session = session
}
// async throws: can fail asynchronously
func fetchOrder(id: String) async throws -> Order {
// Check cache first
if let cached = cache[id] {
return cached
}
let url = baseURL.appendingPathComponent("orders/\(id)")
var request = URLRequest(url: url)
request.setValue("Bearer \(await AuthService.shared.token)", forHTTPHeaderField: "Authorization")
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw OrderError.invalidResponse
}
switch httpResponse.statusCode {
case 200:
let order = try JSONDecoder().decode(Order.self, from: data)
cache[order.id] = order
return order
case 401:
throw OrderError.unauthorized
case 404:
throw OrderError.notFound
default:
throw OrderError.invalidResponse
}
}
func clearCache() {
cache.removeAll()
}
}
Actor Isolation
// Actors/OrderStore.swift — actor for thread-safe mutable state
import Observation
// @Observable macro (Swift 5.9+) works with @MainActor
@MainActor
@Observable
final class OrderStore {
var orders: [Order] = []
var isLoading = false
var error: String?
private let service: OrderService
init(service: OrderService) {
self.service = service
}
// Called on MainActor — safe to mutate @Observable properties
func loadOrders() async {
isLoading = true
error = nil
do {
let fetched = try await service.fetchAllOrders()
orders = fetched
} catch {
self.error = error.localizedDescription
}
isLoading = false
}
func cancelOrder(id: String) async {
do {
let updated = try await service.cancelOrder(id: id)
if let index = orders.firstIndex(where: { $0.id == id }) {
orders[index] = updated
}
} catch {
self.error = "Failed to cancel: \(error.localizedDescription)"
}
}
}
// Non-UI actor: protects shared mutable state from concurrent access
actor RateLimiter {
private var requestCount = 0
private var windowStart = Date()
private let maxRequests: Int
private let windowSeconds: TimeInterval
init(maxRequests: Int = 100, windowSeconds: TimeInterval = 60) {
self.maxRequests = maxRequests
self.windowSeconds = windowSeconds
}
func checkAndIncrement() throws {
let now = Date()
// Reset window if expired
if now.timeIntervalSince(windowStart) > windowSeconds {
requestCount = 0
windowStart = now
}
guard requestCount < maxRequests else {
throw RateLimitError.exceeded
}
requestCount += 1
}
}
TaskGroup for Parallel Work
// Concurrency/ParallelFetcher.swift — structured concurrent work
import Foundation
func fetchOrdersBatch(ids: [String], service: OrderService) async -> [String: Result<Order, Error>] {
await withTaskGroup(of: (String, Result<Order, Error>).self) { group in
// Launch concurrent child tasks
for id in ids {
group.addTask {
do {
let order = try await service.fetchOrder(id: id)
return (id, .success(order))
} catch {
return (id, .failure(error))
}
}
}
// Collect results — order not guaranteed
var results: [String: Result<Order, Error>] = [:]
for await (id, result) in group {
results[id] = result
}
return results
}
}
// withThrowingTaskGroup: cancel all if any throws
func fetchAllRequired(ids: [String], service: OrderService) async throws -> [Order] {
try await withThrowingTaskGroup(of: Order.self) { group in
for id in ids {
group.addTask {
try await service.fetchOrder(id: id)
}
}
var orders: [Order] = []
for try await order in group {
orders.append(order)
}
return orders
}
}
AsyncStream
// Streams/OrderStream.swift — bridge callbacks to AsyncSequence
import Foundation
extension OrderService {
// Bridge URLSession WebSocket to AsyncStream
func orderUpdates(for orderId: String) -> AsyncStream<Order> {
AsyncStream { continuation in
let task = Task {
let url = URL(string: "wss://api.example.com/orders/\(orderId)/stream")!
let (stream, _) = try await URLSession.shared.webSocketTask(with: url)
do {
while !Task.isCancelled {
let message = try await stream.receive()
switch message {
case .string(let text):
if let data = text.data(using: .utf8),
let order = try? JSONDecoder().decode(Order.self, from: data) {
continuation.yield(order)
if order.status == .delivered || order.status == .cancelled {
continuation.finish()
return
}
}
case .data(let data):
if let order = try? JSONDecoder().decode(Order.self, from: data) {
continuation.yield(order)
}
@unknown default:
break
}
}
} catch {
continuation.finish()
}
}
// Called when consumer cancels
continuation.onTermination = { _ in
task.cancel()
}
}
}
}
// Usage: iterate over live order status updates
func trackOrder(id: String) async {
for await order in await orderService.orderUpdates(for: id) {
print("Order \(id) status: \(order.status)")
}
print("Tracking complete")
}
SwiftUI Integration
// Views/OrderDetailView.swift — modern SwiftUI async patterns
import SwiftUI
struct OrderDetailView: View {
let orderId: String
@Environment(OrderStore.self) private var store
@State private var order: Order?
@State private var isLoading = false
@State private var error: String?
var body: some View {
Group {
if isLoading {
ProgressView()
} else if let order {
OrderContent(order: order)
} else if let error {
ContentUnavailableView(error, systemImage: "exclamationmark.triangle")
}
}
.task {
// task modifier: auto-cancelled when view disappears
await loadOrder()
}
.refreshable {
await loadOrder()
}
}
private func loadOrder() async {
isLoading = true
defer { isLoading = false }
do {
order = try await store.service.fetchOrder(id: orderId)
} catch {
self.error = error.localizedDescription
}
}
}
struct OrderListView: View {
@Environment(OrderStore.self) private var store
var body: some View {
List(store.orders) { order in
NavigationLink(value: order) {
OrderRowView(order: order)
}
}
.overlay {
if store.isLoading && store.orders.isEmpty {
ProgressView("Loading orders...")
}
}
.task {
await store.loadOrders()
}
}
}
For the iOS UIKit patterns with completion handlers and delegates that Swift concurrency replaces, see the iOS Swift guide for UIKit integration with withCheckedContinuation. For the Flutter Dart async/await approach that provides similar structured concurrency on the mobile side, the Flutter advanced guide covers Riverpod async state management. The Claude Skills 360 bundle includes Swift concurrency skill sets covering actors, TaskGroup, and SwiftUI async patterns. Start with the free tier to try Swift async code generation.