Thoughts on Combine in an async/await world
When Apple announced their own Functional Reactive Programming framework at WWDC 2019 I was super excited. Finally, a simplified, easy to use framework that we could use to dip our toes in FRP.
What made it even better is that SwiftUI makes heavy use of Combine, which means that Apple had to buy in to the technology themselves. This made it seem unlikely that Apple would abandon the technology any time soon.
Then WWDC 2020 came around and there weren’t any meaningful changes to Combine. It was hardly even mentioned in the sessions. To me, this was a signal that Apple was happy with where Combine’s at and it didn’t need a ton of updates which is absolutely fine.
And then the Swift team started implementing Swift’s overhauled concurrency features like async/await, structured concurrency and even asynchronous collections. Once people saw this, I regularly got questions like “Will these features replace Combine or make Combine obsolete?”.
And I honestly didn’t know what async/await would mean for Combine at the time. And I still don’t know for sure, but I have some thoughts.
Combine is great at observing things and receiving changes. This is also how I often explain Functional Reactive Programming. It’s about responding to changes. When a property changes, a user taps something, or a notification is sent through Notification Center, these are things you might want to receive and react to in some way.
This output can be transformed, and eventually displayed to the user, or used to influence something else in your app. The main idea is that you reacted to a specific change in your app’s environment.
As far as I know, async/await won’t be very well suited to observe an @Published
property, or to listen for notifications in Notification Center. These will most likely be the places where Combine continues to excel and be a fantastic way to react to changes in your app, altough at the same time Async Streams might change this in the future.
On the other hand, there’s a whole range of uses cases where Combine can be used today that will eventually be obsoleted when iOS 15 is your minimum deployment target since async/await is not currently backwards deployable.
An example of this is networking.
Combine (and RxSwift for that matter) is commonly used in networking layers. We treat our data task as publishers, transform their outputs, and eventually we forward this transformed output to our UI for display in one way or the other.
While this is convenient and works well, I don’t think this was ever the best use case for Combine. Swift’s new concurrency features are far better suited for these kinds of tasks that you want to run. After all, Combine never was intended to be a task runner. It was intended to react to streams of values caused by state changes.
I’m curious to see how this unfolds, and I might change my mind on some of this down the line. After all, I’ve only spent a couple of hours seriously looking at Swift’s new concurrency features.
Combine will most likely stick around for a bit, but its uses will for sure be limited once you decide you’re all in on iOS 15 and async/await. Until then, I would recommend you make the best use of Combine you can, and for the time being I won’t be removing networking examples from my Practical Combine book. I will, however update the book to have some callouts where async/await might be a better choice, and I’ll add more examples of where Combine fits in a world where async/await exists.