Skip to main content

Async sequences

About 3 minSwiftArticle(s)bloghackingwithswift.comswiftswift-5.5

Async sequences ꎀ렚

HACKING WITH SWIFT

What's new in Swift?

Async sequences | Changes in Swift 5.5

Async sequences

Available from Swift 5.5

SE-0298 (apple/swift-evolution) introduced the ability to loop over asynchronous sequences of values using a new AsyncSequence protocol. This is helpful for places when you want to process values in a sequence as they become available rather than precomputing them all at once – perhaps because they take time to calculate, or because they aren’t available yet.

Using AsyncSequence is almost identical to using Sequence, with the exception that your types should conform to AsyncSequence and AsyncIterator, and your next() method should be marked async. When it comes time for your sequence to end, make sure you send back nil from next(), just as with Sequence.

For example, we could make a DoubleGenerator sequence that starts from 1 and doubles its number every time it’s called:

struct DoubleGenerator: AsyncSequence {
    typealias Element = Int

    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 1

        mutating func next() async -> Int? {
            defer { current &*= 2 }

            if current < 0 {
                return nil
            } else {
                return current
            }
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator()
    }
}

Tips

If you just remove “async” from everywhere it appears in that code, you have a valid Sequence doing exactly the same thing – that’s how similar these two are.

Once you have your asynchronous sequence, you can loop over its values by using for await in an async context, like this:

func printAllDoubles() async {
    for await number in DoubleGenerator() {
        print(number)
    }
}

The AsyncSequence protocol also provides default implementations of a variety of common methods, such as map(), compactMap(), allSatisfy(), and more. For example, we could check whether our generator outputs a specific number like this:

func containsExactNumber() async {
    let doubles = DoubleGenerator()
    let match = await doubles.contains(16_777_216)
    print(match)
}

Again, you need to be in an async context to use this.

Other Changes in Swift 5.5
Async await | Changes in Swift 5.5

Async await
Effectful read-only properties | Changes in Swift 5.5

Effectful read-only properties
Structured concurrency | Changes in Swift 5.5

Structured concurrency
async let bindings | Changes in Swift 5.5

async let bindings
Continuations for interfacing async tasks with synchronous code | Changes in Swift 5.5

Continuations for interfacing async tasks with synchronous code
Actors | Changes in Swift 5.5

Actors
Global actors | Changes in Swift 5.5

Global actors
Sendable and @Sendable closures | Changes in Swift 5.5

Sendable and @Sendable closures
if for postfix member expressions | Changes in Swift 5.5

if for postfix member expressions
Interchangeable use of CGFloat and Double types | Changes in Swift 5.5

Interchangeable use of CGFloat and Double types
Codable synthesis for enums with associated values | Changes in Swift 5.5

Codable synthesis for enums with associated values
lazy now works in local contexts | Changes in Swift 5.5

lazy now works in local contexts
Extending property wrappers to function and closure parameters | Changes in Swift 5.5

Extending property wrappers to function and closure parameters
Extending static member lookup in generic contexts | Changes in Swift 5.5

Extending static member lookup in generic contexts

Download Swift 5.5 playground