Advanced concurrency patterns for building responsive, efficient Swift applications with async/await.
Task Management
Task Groups
swift
func fetchAllUsers() async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
let ids = ["1", "2", "3", "4", "5"]
for id in ids {
group.addTask {
try await self.fetchUser(id: id)
}
}
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
func fetchWithLimit() async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
let ids = Array(1...100).map { String($0) }
for id in ids {
group.addTask {
try await self.fetchUser(id: id)
}
}
// Process as they complete
var users: [User] = []
for try await user in group {
users.append(user)
if users.count >= 10 {
group.cancelAll()
break
}
}
return users
}
}Task Priorities
swift
func fetchWithPriority() async {
// High priority task
let highPriorityTask = Task(priority: .high) {
await fetchCriticalData()
}
// Background task
let backgroundTask = Task(priority: .background) {
await fetchAnalytics()
}
// User-initiated task
let userTask = Task(priority: .userInitiated) {
await fetchUserData()
}
await highPriorityTask.value
await userTask.value
await backgroundTask.value
}
func checkCancellation() async {
let task = Task {
for i in 0...100 {
try Task.checkCancellation()
print("\(i)")
try? await Task.sleep(nanoseconds: 100_000_000)
}
}
// Cancel after 1 second
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000)
task.cancel()
}
await task.value
}Actor Patterns
Data Isolation
swift
actor DatabaseManager {
private var cache: [String: Data] = [:]
private var accessCount: [String: Int] = [:]
func get(key: String) async -> Data? {
accessCount[key, default: 0] += 1
return cache[key]
}
func set(key: String, value: Data) async {
cache[key] = value
}
func remove(key: String) async {
cache.removeValue(forKey: key)
}
func getStats() async -> (cacheSize: Int, totalAccess: Int) {
(cache.count, accessCount.values.reduce(0, +))
}
}
// Usage
let db = DatabaseManager()
await db.set(key: "user1", value: userData)
let data = await db.get(key: "user1")
let stats = await db.getStats()Actor Reentrancy
swift
actor SafeAccount {
private var balance: Double = 0
func deposit(amount: Double) async {
balance += amount
}
func withdraw(amount: Double) async throws -> Double {
guard balance >= amount else {
throw AccountError.insufficientFunds
}
// Potential suspension point
try await Task.sleep(nanoseconds: 100_000_000)
balance -= amount
return balance
}
func transfer(amount: Double, to other: SafeAccount) async throws {
try await withdraw(amount: amount)
await other.deposit(amount: amount)
}
}Global Actors
swift
@globalActor
struct DatabaseActor {
static let shared = DatabaseActorExecutor()
}
actor DatabaseActorExecutor {
// Main actor executor
}
@DatabaseActor
class UserRepository {
func fetchUser(id: String) async throws -> User {
// Automatically runs on DatabaseActor
try await database.query("SELECT * FROM users WHERE id = \(id)")
}
}AsyncStream Patterns
Creating Streams
swift
func createNumberStream() -> AsyncStream<Int> {
return AsyncStream { continuation in
Task {
for i in 1...10 {
continuation.yield(i)
try? await Task.sleep(nanoseconds: 500_000_000)
}
continuation.finish()
}
}
}
func processStream() async {
let stream = createNumberStream()
for await number in stream {
print("\(number)")
}
}AsyncThrowingStream
swift
func createErrorStream() -> AsyncThrowingStream<Int, Error> {
return AsyncThrowingStream { continuation in
Task {
for i in 1...10 {
if i == 5 {
continuation.finish(throwing: StreamError.invalidValue)
return
}
continuation.yield(i)
}
continuation.finish()
}
}
}
func processWithError() async throws {
let stream = createErrorStream()
do {
for try await number in stream {
print("\(number)")
}
} catch {
print("Error: \(error)")
}
}Stream Buffering
swift
enum StreamError: Error {
case overflow
}
func createBufferedStream() -> AsyncStream<Int> {
return AsyncStream(
Int.self,
bufferingPolicy: .bufferingNewest(10)
) { continuation in
// Producer
}
}Continuation Patterns
Unsafe Continuation
swift
func fetchUserLegacy(id: String) async throws -> User {
try await withUnsafeThrowingContinuation { continuation in
legacyAPI.fetchUser(id: id) { result in
switch result {
case .success(let user):
continuation.resume(returning: user)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
func fetchWithTimeout(timeout: TimeInterval) async throws -> User {
try await withThrowingTaskGroup(of: User.self) { group in
group.addTask {
try await fetchUserLegacy(id: "123")
}
group.addTask {
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
throw APIError.timeout
}
guard let user = try await group.next() else {
throw APIError.unknown
}
group.cancelAll()
return user
}
}Synchronization
Async Lock
swift
actor AsyncLock {
private var isLocked = false
private var waiters: [CheckedContinuation<Void, Never>] = []
func acquire() async {
if !isLocked {
isLocked = true
return
}
await withCheckedContinuation { continuation in
waiters.append(continuation)
}
}
func release() {
guard let waiter = waiters.first else {
isLocked = false
return
}
waiters.removeFirst()
waiter.resume()
}
}
func useLock() async {
let lock = AsyncLock()
await lock.acquire()
defer { await lock.release() }
// Critical section
}Async Barrier
swift
actor AsyncBarrier {
private var arrived = 0
private var waiters: [CheckedContinuation<Void, Never>] = []
private let count: Int
init(count: Int) {
self.count = count
}
func wait() async {
arrived += 1
if arrived == count {
// Release all waiters
for waiter in waiters {
waiter.resume()
}
waiters.removeAll()
arrived = 0
} else {
await withCheckedContinuation { continuation in
waiters.append(continuation)
}
}
}
}Cancellation
Cooperative Cancellation
swift
func cooperativeTask() async {
await withTaskCancellationHandler(
operation: {
for i in 0...100 {
try? Task.checkCancellation()
print("\(i)")
try? await Task.sleep(nanoseconds: 100_000_000)
}
},
onCancel: {
print("Task was cancelled")
}
)
}Cancellation Propagation
swift
func parentTask() async {
let childTask1 = Task {
await childTask()
}
let childTask2 = Task {
await anotherChildTask()
}
// Cancel propagates to children
childTask1.cancel()
await childTask1.value
await childTask2.value
}Best Practices
- 1Structured Concurrency: Use structured tasks
- 2Cancellation: Handle cancellation properly
- 3Actor Isolation: Protect shared state
- 4Error Handling: Handle async errors
- 5Testing: Test async code
- 6Performance: Profile async code
- 7Memory: Manage memory properly
- 8Debugging: Debug concurrency issues
Master concurrency for responsive apps!