Create one mutable iterator and call await iterator.next() for each successive value. For a small Mac command-line calculation, keep both the stream and iterator outside the repeated read operation, and treat nil as completion.
This walkthrough consumes the synthetic integers 4, 7, and 10, squares each value, and checks that the total is 165. The calculation uses only values created in memory. Its purpose is to make iterator lifetime, buffering, and completion visible in one small program.
What actually restarts when values repeat?
Swift separates an asynchronous sequence from its iterator. The sequence supplies an iterator through makeAsyncIterator(), and the iterator advances through calls to next(). Each call returns an element or nil when iteration has ended. Apple documents this contract in AsyncIteratorProtocol.
There are two different operations to inspect when an application repeatedly receives the first value: constructing another iterator and constructing another stream. They are not interchangeable. In the current standard-library implementation, an AsyncStream iterator holds the stream’s shared context, and makeAsyncIterator() passes that existing context into the iterator. Making another iterator on the same stream therefore does not create an independent replay from the beginning. See the official AsyncStream implementation.
By contrast, calling a factory that creates a new stream and yields 4, 7, and 10 creates another source containing those values. If every read calls that factory and consumes just one element, every read can receive 4. A computed property that constructs a stream in its getter can have the same effect. For this task, construct the source once and keep one iterator for the entire calculation.
Use this distinction when deciding where to move code: move stream creation outside the repeated operation to preserve the source, and move iterator creation outside it to preserve one clear consumption path. Do not rely on repeated iterator creation as either a restart mechanism or a way to maintain progress.
Run a complete local calculation on your Mac
Use a Mac with a Swift 6 or newer toolchain. In Terminal, check the installed compiler, then create a small executable package in a new directory. Swift.org’s Build a Command-line Tool guide documents the version check, package creation, and swift run workflow.
swift --version
mkdir IteratorDemo
cd IteratorDemo
swift package init --name IteratorDemo --type executable
Find the generated executable source under Sources. Replace its contents with the complete program below. Name that source file IteratorDemo.swift; if the template created main.swift, rename it and remove its original contents. Keep only one executable entry point. Swift.org documents that a main.swift entry point and an @main entry point cannot be combined in the same executable.
No imports are needed for this example: AsyncStream belongs to Swift, while Foundation is a separate framework. The calculation needs no Foundation API. These boundaries appear in Apple’s AsyncStream documentation and Foundation overview.
@main
struct IteratorDemo {
static func makeValues() -> AsyncStream<Int> {
AsyncStream<Int>(bufferingPolicy: .bufferingOldest(3)) {
continuation in
for value in [4, 7, 10] {
let result = continuation.yield(value)
guard case .enqueued = result else {
preconditionFailure("A sample value was not enqueued")
}
}
continuation.finish()
}
}
static func main() async {
let stream = makeValues()
var iterator = stream.makeAsyncIterator()
var received: [Int] = []
var squareTotal = 0
for readNumber in 1...3 {
guard let value = await iterator.next() else {
preconditionFailure("Stream ended before read \(readNumber)")
}
received.append(value)
squareTotal += value * value
print("Read \(readNumber): \(value)")
}
let end = await iterator.next()
let stillEnded = await iterator.next()
precondition(received == [4, 7, 10])
precondition(squareTotal == 165)
precondition(end == nil)
precondition(stillEnded == nil)
print("Square total: \(squareTotal)")
print("Finished: \(end == nil)")
}
}
Run the executable from the package directory:
swift run IteratorDemo
The expected application output, after any build messages, is:
Read 1: 4
Read 2: 7
Read 3: 10
Square total: 165
Finished: true
The arithmetic is explicitly illustrative: 4 × 4 + 7 × 7 + 10 × 10 = 165. The assertions check the exact values, their order, the calculation, and terminal behavior. A Swift compiler was unavailable in the research environment, so this program was inspected but not executed here.
Why does this iterator continue correctly?
The program calls makeValues() once and makeAsyncIterator() once. Its three reads all use the same mutable variable. Apple declares the iterator’s next() method as mutating and asynchronous, which explains both var iterator and await iterator.next().
The producer yields its three values during stream construction, before consumption starts. Buffer capacity is therefore part of the example’s contract: .bufferingOldest(3) has room for all three integers. Checking the result of every yield makes an unexpected enqueue failure visible rather than allowing the calculation to proceed with missing values. Apple documents that yield(_:) attempts to buffer a value when no read is waiting.
Calling finish() after those yields closes production while allowing buffered values to be consumed. The accepted Swift proposal explicitly states that buffered elements reach the consumer before terminal nil. That is why the first three reads succeed even though the producer has already finished. See AsyncStream and AsyncThrowingStream, SE-0314.
The fourth read finds the finished, exhausted stream and returns nil. The fifth confirms the terminal contract: after an iterator has returned nil, subsequent reads must also return nil. Do not interpret nil as an instruction to retry later or reconstruct an iterator; AsyncIteratorProtocol defines it as the end of iteration.
Choose explicit reads or one continuous loop
Use explicit next() calls when the surrounding operation needs control between individual reads, as the numbered calculation does. Keep the iterator in the scope that owns the whole consumption session. A helper that creates its own iterator on every invocation obscures that ownership; a helper that also reconstructs the stream changes the source itself.
When the task is simply to process every element until completion, use one for await loop. Apple’s AsyncStream overview demonstrates this supported consumption form. For the sample program, the following is a replacement fragment for the entire existing main() method; retain the existing makeValues() method and surrounding type:
static func main() async {
let stream = makeValues()
var received: [Int] = []
var squareTotal = 0
for await value in stream {
received.append(value)
squareTotal += value * value
}
precondition(received == [4, 7, 10])
precondition(squareTotal == 165)
print("Square total: \(squareTotal)")
}
This version expresses a single continuous consumption session. Choose it when no separate read commands are required. Choose the explicit iterator when individual reads are part of the task’s structure. Both approaches still need a correctly constructed source and a defined end.
Diagnose repeated, missing, or unfinished reads
The following sequence separates source lifetime problems from buffering and completion problems. Apply it to the fixed sample before adding more behavior.
- Repeated
4: inspect source construction. Check whether each operation callsmakeValues()again, or accesses a getter that creates a new stream. Keep the factory call outside the repeated reads. - Unexpected values across consumers: inspect ownership. Give this calculation one consumer and one iterator. Multiple iterators on the same stream do not provide independent copies of its values.
- Missing values: inspect buffer capacity. The sample produces all three integers before consumption. A smaller buffer changes which values remain available.
- A final read that never completes: inspect completion. In a continuation-based stream, an empty buffer alone does not finish production. Ensure the finite producer calls
finish(). - Overlapping reads: serialize consumption. Await one read before beginning another on the iterator.
Buffer changes have predictable consequences under this example’s conditions. With .bufferingOldest(2), the first two values remain and the new value 10 is dropped. With .bufferingNewest(2), the oldest buffered value 4 is discarded, leaving 7 and 10. These are deductions for the stated production order from Apple’s documented bufferingOldest(_:) and bufferingNewest(_:) policies.
Neither policy makes the producer wait for consumption. Apple documents that yield(_:) returns immediately, so retaining one iterator cannot compensate for an undersized buffer. For this finite three-element calculation, capacity three is sufficient; a different production pattern requires its own capacity decision.
Keep iterator access sequential. Apple states that AsyncStream.Iterator does not conform to Sendable and that contending concurrent calls to next() are a programmer error. Cancellation also affects continuation: if the consuming task is canceled while awaiting a value, Apple’s next() documentation says the stream terminates. Creating another iterator is not a documented way to resume that canceled stream.
For this local calculation, the complete consumption contract is small: create one source, retain one mutable iterator, await reads sequentially, preserve all three values in the buffer, and stop at nil. Those choices make progress explicit and give the final total a checkable meaning.
Sources
- AsyncStream — Apple.
- AsyncIteratorProtocol — Apple.
- AsyncStream.Iterator — Apple.
- next() — Apple.
- yield(_:) — Apple.
- AsyncStream.Continuation.BufferingPolicy.bufferingOldest(_:) — Apple.
- AsyncStream.Continuation.BufferingPolicy.bufferingNewest(_:) — Apple.
- Foundation — Apple.
- Build a Command-line Tool — Swift.org.
- AsyncStream.swift — Swift project.
- AsyncStream and AsyncThrowingStream, SE-0314 — Swift project.