Friday, August 14, 2026
HomeiOS DevelopmentEvaluating use circumstances for async sequences and publishers – Donny Wals

Evaluating use circumstances for async sequences and publishers – Donny Wals

[ad_1]

Printed on: April 12, 2022

Swift 5.5 introduces async/await and a complete new concurrency mannequin that features a new protocol: AsyncSequence. This protocol permits builders to asynchronously iterate over values coming from a sequence by awaiting them. Which means that the sequence can generate or receive its values asynchronously over time, and supply these values to a for-loop as they change into accessible.

If this sounds acquainted, that’s as a result of a Mix writer does roughly the identical factor. A writer will receive or generate its values (asynchronously) over time, and it’ll ship these values to subscribers at any time when they’re accessible.

Whereas the idea of what we are able to do with each AsyncSequence and Writer sounds comparable, I wish to discover among the variations between the 2 mechanisms in a sequence of two posts. I’ll focus this comparability on the next matters:

  • Use circumstances
  • Lifecycle of a subscription / async for-loop

The submit you’re studying now will give attention to evaluating use circumstances. If you wish to study extra about lifecycle administration, check out this submit.

Please notice that components of this comparability can be extremely opinionated or be primarily based on my experiences. I’m making an attempt to be sure that this comparability is honest, trustworthy, and proper however in fact my experiences and preferences will affect a part of the comparability. Additionally notice that I’m not going to invest on the futures of both Swift Concurrency nor Mix. I’m evaluating AsyncSequence to Writer utilizing Xcode 13.3, and with the Swift Async Algorithms package deal added to my undertaking.

Let’s dive in, and try some present use circumstances the place publishers and async sequences can actually shine.

Operations that produce a single output

Our first comparability takes a more in-depth take a look at operations with a single output. Whereas it is a acquainted instance for many of us, it isn’t the perfect comparability as a result of async sequences aren’t made for performing work that produces a single end result. That’s to not say an async sequence can’t ship just one end result, it completely can.

Nonetheless, you sometimes wouldn’t leverage an async sequence to make a community name; you’d await the results of an information job as a substitute.

Then again, Mix doesn’t differentiate between duties that produce a single output and duties that produce a sequence of outputs. Which means that publishers are used for operations that may emit many values in addition to for values that produce a single worth.

Mix’s strategy to publishers could be thought-about an enormous advantage of utilizing them since you solely have one mechanism to study and perceive; a writer. It may also be thought-about a draw back since you by no means know whether or not an AnyPublisher<(Knowledge, URLResponse), Error> will emit a single worth, or many values. Then again, let end result: (Knowledge, URLResponse) = attempt await getData() will all the time clearly produce a single end result as a result of we don’t use an async sequence to acquire a single end result; we await the results of a job as a substitute.

Despite the fact that this comparability technically compares Mix to async/await reasonably than async sequences, let’s check out an instance of performing a community name with Mix vs. performing one with async/await to see which one appears extra handy.

Mix:

var cancellables = Set<AnyCancellable>()

func getData() {
    let url = URL(string: "https://donnywals.com")!
    URLSession.shared.dataTaskPublisher(for: url)
        .sink(receiveCompletion: { completion in
            if case .failure(let error) = completion {
                // deal with error
            }
        }, receiveValue: { (end result: (Knowledge, URLResponse)) in
            // use end result
        })
        .retailer(in: &cancellables)
}

Async/Await:

func getData() async {
    let url = URL(string: "https://donnywals.com")!
    do {
        let end result: (Knowledge, URLResponse) = attempt await URLSession.shared.knowledge(from: url)
        // use end result
    } catch {
        // deal with error
    }
}

For my part it’s fairly clear which expertise is extra handy for performing a job that produces a single end result. Async/await is simpler to learn, simpler to make use of, and requires far much less code.

With this considerably unfair comparability out of the best way, let’s check out one other instance that permits us to extra immediately examine an async sequence to a writer.

Receiving outcomes from an operation that produces a number of values

Operations that produce a number of values are available many shapes. For instance, you may be utilizing a TaskGroup from Swift Concurrency to run a number of duties asynchronously, receiving the end result for every job because it turns into accessible. That is an instance the place you’d use an async sequence to iterate over your TaskGroup‘s outcomes. Sadly evaluating this case to Mix doesn’t make lots of sense as a result of Mix doesn’t actually have an equal to TaskGroup.

💡 Tip: to study extra about Swift Concurrency’s TaskGroup check out this submit.

One instance of an operation that may produce a number of values is observing notifications on NotificationCenter. This can be a good instance as a result of not solely does NotificationCenter produce a number of values, it’s going to accomplish that asynchronously over a protracted time period. Let’s check out an instance the place we observe modifications to a person’s gadget orientation.

Mix:

var cancellables = Set<AnyCancellable>()

func notificationCenter() {
    NotificationCenter.default.writer(
        for: UIDevice.orientationDidChangeNotification
    ).sink(receiveValue: { notification in
        // deal with notification
    })
    .retailer(in: &cancellables)
}

AsyncSequence:

func notificationCenter() async {
    for await notification in await NotificationCenter.default.notifications(
        named: UIDevice.orientationDidChangeNotification
    ) {
        // deal with notification
    }
}

On this case, there’s a bit much less of a distinction than once we used async/await to acquire the results of a community name. The principle distinction is in how we obtain values. In Mix, we use sink to subscribe to a writer and we have to maintain on to the supplied cancellable so the subscription is stored alive. With our async sequence, we use a particular for-loop the place we write for await <worth> in <sequence>. Every time a brand new worth turns into accessible, our for-loop’s physique known as and we are able to deal with the notification.

Should you take a look at this instance in isolation I don’t assume there’s a really clear winner. Nonetheless, once we get to the convenience of use comparability you’ll discover that the comparability on this part doesn’t inform the complete story when it comes to the lifecycle and implications of utilizing an async sequence on this instance. The subsequent a part of this comparability will paint a greater image relating to this subject.

Let’s take a look at one other use case the place you may end up questioning whether or not you must attain for Mix or an async sequence; state remark.

Observing state

Should you’re utilizing SwiftUI in your codebase, you’re making in depth use of state remark. The combination of @Printed and ObservableObject on knowledge sources exterior to your view permit SwiftUI to find out when a view’s supply of fact will change so it may doubtlessly schedule a redraw of your view.

💡 Tip: If you wish to study extra about how and when SwiftUI determined to redraw views, check out this submit.

The @Printed property wrapper is a particular sort of property wrapper that makes use of Mix’s CurrentValueSubject internally to emit values proper earlier than assigning these values because the wrapped property’s present worth. This implies which you can subscribe to @Printed utilizing Mix’s sink to deal with new values as they change into accessible.

Sadly, we don’t actually have an identical mechanism accessible that solely makes use of Swift Concurrency. Nonetheless, for the sake of the comparability, we’ll make this instance work by leveraging the values property on Writer to transform our @Printed writer into an async sequence.

Mix:

@Printed var myValue = 0

func stateObserving() {
    $myValue.sink(receiveValue: { newValue in

    }).retailer(in: &cancellables)
}

Async sequence:

@Printed var myValue = 0

func stateObserving() async {
    for await newValue in $myValue.values {
        // deal with new worth
    }
}

Just like earlier than, the async sequence model appears somewhat bit cleaner than the Mix model however as you’ll discover in the following submit, this instance doesn’t fairly inform the complete story of utilizing an async sequence to watch state. The lifecycle of an async sequence can, in sure case complicate our instance rather a lot so I actually suggest that you just additionally try the lifecycle comparability to realize a a lot better understanding of an async sequence’s lifecycle.

It’s additionally vital to remember that this instance makes use of Mix to facilitate the precise state remark as a result of at the moment Swift Concurrency doesn’t present us with a built-in approach to do that. Nonetheless, by changing the Mix writer to an async sequence we are able to get a fairly good sense of what state remark might seem like if/when assist for that is added to Swift.

Abstract

On this submit, I’ve coated three totally different use circumstances for each Mix and async sequences. It’s fairly clear that iterating over an async sequence appears a lot cleaner than subscribing to a writer. There’s additionally little doubt that duties with a single output like community calls look a lot cleaner with async/await than they do with Mix.

Nonetheless, these examples aren’t fairly as balanced as I might have favored them to be. In all the Mix examples I took into consideration the lifecycle of the subscriptions I created as a result of in any other case the subscriptions wouldn’t work as a result of cancellable that’s returned by sink being deallocated if it’s not retained in my set of cancellables.

The async sequence variations, nevertheless, work positive with none lifecycle administration however there’s a catch. Every of the features I wrote was async which signifies that calling these features have to be achieved with an await, and the caller is suspended till the async sequence that we’re iterating over completes. Within the examples of NotificationCenter and state remark the sequences by no means finish so we’ll must make some modifications to our code to make it work with out suspending the caller.

We’ll take a greater take a look at this within the subsequent submit.

[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments