Skip to content

Task Groups (SwiftUI)

A SwiftUI guide for coding agents. Also covers withtaskgroup, parallel downloads swift, concurrency limit task group, cancelall task group, addtaskunlesscancelled, structured concurrency child tasks.

withTaskGroup runs a dynamic number of child tasks in parallel and collects their results. The group is an AsyncSequence, so you await results with a for await loop. Back-deployed to iOS 13 and macOS 10.15.

The basic shape

The first parameter is each child's result type, the second the accumulated return type, and the closure populates the group with addTask.

func fetch(urls: [URL]) async {
  messages = await withTaskGroup(of: String.self, returning: [String].self) { group in
    for url in urls {
      group.addTask(priority: .high) { await fetch(url) }
    }

    var messages: [String] = []
    for await message in group where !message.isEmpty {
      messages.append(message)
    }
    return messages
  }
}

The group does not inherit the parent's actor. Child tasks run on the cooperative thread pool, so a @MainActor caller does not drag the work onto the main thread.

Cancellation is cooperative

Cancelling the group, with cancelAll() or through the parent task, does not stop a child by itself. Check Task.isCancelled inside the child and bail out.

group.addTask {
  if Task.isCancelled { return "" }
  return await fetch(url)
}

for await message in group where !message.isEmpty {
  messages.append(message)
  if messages.count > 3 { group.cancelAll() }   // stop after enough results
}

Bound the in-flight tasks

Adding 1000 children at once allocates 1000 suspended tasks while the pool works through them, which spikes memory. Seed a small batch, then add one task each time a result arrives. addTaskUnlessCancelled skips the add once the group is cancelled.

let maxConcurrentRequests = min(urls.count, 10)
var index = -1

for _ in 0..<maxConcurrentRequests {
  group.addTask { index += 1; return await self.fetch(urls[index]) }
}

for await message in group where !message.isEmpty {
  messages.append(message)
  if index < urls.count {
    group.addTaskUnlessCancelled { index += 1; return await self.fetch(urls[index]) }
  }
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-mastering-task-groups-in-swift" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems