What Swift actually waits for
A loop, three uploads and one useful question: when the code says “done,” what has actually finished?
Swift playground, automated check and source code · ZIP
for-in + awaitThe next item waits for the current call.
forEach + saved Task handlesYou decide when to await each task.
withTaskGroupThe group scope waits for its children.
Start with for-in and forEach
Both visit an array. A for-in loop gives you break, continue and an await point. forEach calls a synchronous closure for each item.
for item in cart {
if item == "Banana" { break }
print(item)
}Prints Apple.cart.forEach { item in
if item == "Banana" { return }
print(item)
}Prints Apple, Mango.break exits the loop. return exits only Banana’s closure call; forEach continues with Mango. That is why return inside forEach is not a way to stop the entire iteration.
When order matters, await in the loop
for item in cart {
_ = await upload(item)
}
Apple’s call returns before Banana starts, and Banana’s returns before Mango starts. Standard forEach cannot put await upload(item) directly in its synchronous closure.
Save handles when you want control later
var handles: [Task<UploadOutcome, Never>] = []
cart.forEach { item in
handles.append(Task {
await upload(item, shouldFail: item == "Banana")
})
}
print("caller continued")
var handleOutcomes: [UploadOutcome] = []
for handle in handles {
handleOutcomes.append(await handle.value)
}
forEach returns after creating the tasks. The caller moves on while uploads may still be running. Each saved handle lets you wait for that task’s result. Awaiting every handle tells you when all three have ended.
The last for loop does not make uploads sequential. All three tasks were created before it began. You are waiting for already-running work.
Let a task group own the batch
var groupOutcomes: [UploadOutcome] = []
await withTaskGroup(of: UploadOutcome.self) { group in
cart.forEach { item in
group.addTask {
await upload(item, shouldFail: item == "Banana")
}
}
for await outcome in group {
groupOutcomes.append(outcome)
}
}
print("group returned")
These uploads also overlap. The for await loop collects results as children finish; their order may differ from the cart’s order. The group scope waits for every child before returning, even if you do not collect results.
Ask tasks to stop, then await their response
With saved handles, cancel the tasks you own individually:
handles.forEach { $0.cancel() }
for handle in handles {
_ = await handle.value
}
Inside a group, group.cancelAll() requests cancellation of its remaining children. The playground calls it after the first upload finishes. From outside, save a handle to the parent Task, then call parent.cancel(); cancellation propagates to its group children.
_ = await group.next() // one child finished
group.cancelAll() // ask the others to stop
Cancellation is cooperative. It is a request, not an instant stop. The playground’s simulated upload waits with Task.sleep, which responds to cancellation; real upload code must do the same. A group still waits for children that have not ended.
“All ended” does not mean “all succeeded”
Banana deliberately fails in both concurrent playground examples. Both approaches report the same result: 2 succeeded, 1 failed. Waiting tells you the work ended; the returned UploadOutcome tells you what happened.
A real upload should return a result or throw. Do not show “Upload complete” simply because every task has stopped.
Why forEach is a higher-order function
forEach accepts a function—the closure you write—as input. It owns the traversal; you supply the action. That is the purpose of a higher-order function: reuse the repeated procedure while changing the rule or action.
The playground also defines countWhere. It visits every element and counts those matching a rule you pass in:
let longNames = cart.countWhere { $0.count > 5 }
// 1: Banana
Run the same examples
The ZIP contains the Xcode playground, source, and an automated check that executes the playground page. Start with the for-in example, then compare the two ways of waiting for all uploads.