I spent a few years building iOS apps with ReactiveSwift before Combine existed, and when I made the switch to Combine I kept noticing small ergonomic gaps. Combine got most of the big things right, but it was missing a handful of types and patterns that ReactiveSwift had refined over years of production use. CombineEx is the layer of those things I kept re-implementing in every project.
The biggest additions:
Property and MutableProperty — basically CurrentValueSubject with
two changes: the failure type is locked to Never, so you can’t
accidentally complete the stream by emitting an error, and MutableProperty
guarantees thread-safe imperative access to the underlying value. If you’re
using a reactive type as application state (which you almost always are),
CurrentValueSubject lets too much go wrong.Action — a deferred-publisher factory for “this work shouldn’t run
twice in parallel” cases. Multiple invocations of the same Action
serialize, which is what you want for button taps that kick off network
calls.AnyDeferredPublisher — Combine doesn’t distinguish hot from cold
publishers in any explicit way. AnyDeferredPublisher makes “cold” a
type, so the work re-runs per subscription instead of being shared
silently.UIScheduler — runs synchronously on the main thread when you’re
already on the main thread, instead of always async-dispatching like
DispatchQueue.main.eraseToAnyScheduler() does. Saves a frame in a lot
of UI paths.CombineEx is also what Mortar 3 used for its reactive bindings, and the patterns there ended up influencing how I thought about screen architecture later.