Swift Async Sequence extensions (part 2)
In Swift Async Sequence extensions (part 1) I covered simple ways to create an async sequence using a custom factory method and binding a sequence to a UI control via a custom
This covered the “beginning” and “end” of the data stream (so to say) but what about processing or converting the data along the way? If you draw a parallel to Combine code — how would you build custom “operators” for your async sequence?
In this post I’ll show you exactly that — building a method that converts one async sequence into another.
Existing AsyncSequence methods
Plenty of the new Swift Concurrency code is freely available at Apple’s GitHub so should you want to peak in you can.
For example here’s the
AsyncSequence.filter(_:) source code:
- It defines an extension on
- Adds the
- And finally declares a type called
AsyncFilterSequence— this is what the
This is very similar setup to what the Combine operators do — most often they are a method on the
Publisher protocol and return some kind of custom publisher that implements the operator logic. So no surprises here.
Note: The whole repo is very interesting to read through. For example have a look at how
Building a custom async sequence method
For this article I’ll build an alternative of the
Publisher.replaceNil(_:) method from Combine. It’s a handy operator that replaces any
nil values in the stream with a given constant.
Following the pattern established in the files I linked above, I’ll start by creating a custom async sequence type called
This new sequence
AsyncReplaceNilSequence is generic (
T) over its elements. An additional
where clause requires that the base sequence has optional values (
T?). This way if the base sequence contains, for example, elements
String? — the resulting new sequence will have all
nil values replaced and the output elements will be of type
Next, I need a proper initializer that will take the base sequence and the constant to replace
nil values with:
base holds onto the “source” sequence (or the base sequence) and
replaceValue is a constant to replace
nil values with.
The final step in building the sequence is to define the sequence iterator. First I’ll add the sequence method that creates the iterator:
And then I’ll add
Iterator as a nested structure inside
Very similarly to the sequence type itself, the iterator also holds on to its base counterpart and the value to use for replacing.
The meaningful step here is defining the iterator
next() method that will pull values from the base sequence and replay them while replacing any
The method fetches the
next() value from its base iterator and in case the result is
nil it returns the replace constant instead.
So now I have an async sequence and a working iterator. I only need the method on
AsyncSequence before I wrap up. I chose to mimic the method signature of Combine’s
replaceNil() like so:
The method is generic over its parameter type and that allows it to accept a
String parameter if the sequence is
String? or an
Int parameter if the sequence is
Int?, and not be available at all if the sequence is not of an optional element type.
Trying the code in a view
Let’s give the code a try. I’ll reuse my code from part 1 including my
AsyncSequence.assign() methods that create a sequence of values over time and bind them to a label on screen.
I’ll use an
[Int?] array to display some numbers on screen:
map to convert the optional number to a string before binding it to the
@State property. That displays the description of the optional instead of the value by default:
And this is one of the common use cases that
replaceNil(_:) is good for — if you have a fallback value for
nil you can unwrap the sequence by removing the optional from the generic and bind the sequence directly like so:
And that’s all for today! I this code and spelling out the steps was useful — you can find the complete source code below.
The completed source code
This is a whole bunch of code but including it below in case you’d like to copy it over and play with it:
One final note — when you’re building a method like this for production it makes sense to optimize for performance and add inline-able decorators. If you want to get some insight on how Apple’s done that have a look at
AsyncSequence.map’s source code.
Where to go from here?
If you’d like to support me, get my book on Swift Concurrency:
Interested in discussing the new
await Swift syntax and concurrency? Hit me up on twitter at https://twitter.com/icanzilb.