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 assign
method.
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
AsyncSequence
- Adds the
filter
method - And finally declares a type called
AsyncFilterSequence
— this is what thefilter
method returns.
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.
If you want inspiration for build your own async sequence methods you can check out map
, prefix
, and so on.
Note: The whole repo is very interesting to read through. For example have a look at how
Task.sleep(...)
looks like.
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 AsyncReplaceNilSequence
:
|
|
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 String
.
Next, I need a proper initializer that will take the base sequence and the constant to replace nil
values with:
|
|
The property 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 AsyncReplaceNilSequence
:
|
|
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 nil
values:
|
|
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 Array.spread()
and 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:
|
|
I’m using 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 async
/await
Swift syntax and concurrency? Hit me up on twitter at https://twitter.com/icanzilb.