By Casey Liss
Building Up to Combine

I’ve been preaching the gospel about RxSwift for a year and a half now. RxSwift took me quite a while to get my head around, but once it clicked, there was no going back for me. I now have the shiniest hammer in the world, and I’ll be damned if everything doesn’t resemble a nail.

A little over a week ago, at WWDC, Apple unveiled their Combine framework. At a glance, Combine seems like little more than a first-party take on RxSwift. Before I can really get into what I do and don’t like about it, we need to understand what problem Combine is setting out to solve.

Reactive Programming? What now?

The ReactiveX community — the community which RxSwift is a part of — summarizes itself as follows:

An API for asynchronous programming with observable streams

And further:

ReactiveX is a combination of the best ideas from the Observer pattern, the Iterator pattern, and functional programming

Um… k. 👌🏻

So what the hell does that really mean?

A Foundation

In order to really understand what reactive programming is about, I find it helpful to understand how we got here. In this post, I’ll describe how one can look at existing types in any modern object-oriented programming language, twist them around, and land on reactive programming.

This post gets in the weeds fast and isn’t absolutely necessary to understanding reactive programming.

However, I do think it’s a fascinating academic exercise, especially in how strongly typed languages can lead us down the path to new discoveries.

So, feel free to wait until my next post if this goes too far off in the weeds for you.

Enumerables

The “reactive programming” that I know was borne from my old language of choice, C#. The whole premise, distilled, is fairly simple:

What if instead of enumerables where you pull values out, you instead got values pushed to you?

This push rather than pull idea was best described to me in an incredible video with Brian Beckman and Erik Meijer. The first ~36 minutes is… over my head, but starting at around 36 minutes, things get really interesting.

In short, let’s re-define the idea of a linear group of objects in Swift, as well as an object that can iterate across that linear group. We can do so by defining these fake Swift protocols:

// A linear group of objects; you could easily imagine this 
// being backed by an Array.
protocol Enumerable {
    associatedtype Enum: Enumerator
    associatedtype Element where Self.Element == Self.Enum.Element
    
    func getEnumerator() -> Self.Enum
}

// An object that can walk across a linear group of objects.
protocol Enumerator: Disposable {
    associatedtype Element
    
    func moveNext() throws -> Bool
    var current: Element { get }
}

// We may eventually need to clean up our 
// Enumerator; it could be operating on files,
// or a network resource. This is how we do so.
protocol Disposable {
    func dispose()
}

Duality

Let’s flip all of those, or make their duals. So where data was coming out, we put data in. Where data was going in, we pull data out. This sounds funny, but bear with me.

Duality of Enumerable

Starting with Enumerable:

// Exactly as seen above.
protocol Enumerable {
    associatedtype Element where Self.Element == Self.Enum.Element
    associatedtype Enum: Enumerator
    
    func getEnumerator() -> Self.Enum
}

protocol DualOfEnumerable {
    // Enumerator has:
    // getEnumerator() -> Self.Enum
    // Which could be rewritten as:
    // getEnumerator(Void) -> Enumerator
    //
    // Thus, we could summarize:
    // IN: Void; OUT: Enumerator
    // getEnumerator(Void) → Enumerator
    //
    // Thus, we are taking in Void and emitting an Enumerator.
    // For the dual of this, we should take IN whatever the
    // dual of an Enumerator is, and emit Void.
    // IN: Dual of Enumerator; OUT: Void
    func subscribe(DualOfEnumerator)
}

Again, since getEnumerator() took in Void and emitted an Enumerator, we are instead taking in [the dual of] Enumerator and emiting/returning Void.

I know this is weird. Stick with me here.

Duality of Enumerator

So what is DualOfEnumerator then?

// Exactly as seen above.
protocol Enumerator: Disposable {
    associatedtype Element
    
    // IN: Void; OUT: Bool, Error
    func moveNext() throws -> Bool
    // IN: Void; OUT: Element
    var current: Element { get }
}

protocol DualOfEnumerator {
    // IN: Bool, Error; OUT: Void
    // The previously thrown Error we will ignore for a moment
    func enumeratorIsDone(Bool)
    // IN: Element, OUT: Void
    var nextElement: Element { set }
}

Now, a few problems here:

  • Swift doesn’t have the concept of a set-only property
  • What happened to the throws on Enumerator.moveNext()?
  • What about Disposable? What happens with that?

To fix the set-only property, we can treat a set-only property as what it really is: a func. Thus, let’s slightly tweak our DualOfEnumerator:

protocol DualOfEnumerator {
    // IN: Bool; OUT: Void, Error
    // The previously thrown Error we will ignore for a moment
    func enumeratorIsDone(Bool)
    // IN: Element, OUT: Void
    func next(Element)
}

To fix the throws, let’s break out the error that could happen in moveNext() and treat it as its own separate func called error():

protocol DualOfEnumerator {
    // IN: Bool, Error; OUT: Void
    func enumeratorIsDone(Bool)
    func error(Error)
    
    // IN: Element, OUT: Void
    func next(Element)
}

There’s one other change we can make: look at the signature for when we’ve finished enumerating:

func enumeratorIsDone(Bool)

Presumably we’d have something like this over time:

enumeratorIsDone(false)
enumeratorIsDone(false)
// Now we're finally done
enumeratorIsDone(true)

At that point, why not just simplify things and only call enumeratorIsDone when… things are done? We can take that approach, and simplify the signature:

protocol DualOfEnumerator {
    func enumeratorIsDone()
    func error(Error)
    
    func next(Element)
}

Cleaning Up Our Mess

Finally, what about that Disposable? What do we do with that? Well, since Disposable is a part of the type Enumerator, when we get the dual of Enumerator, perhaps it shouldn’t be on Enumerator at all. Instead, it should be a part of DualOfEnumerable. But where?

The place where we are taking in the DualOfEnumerator is here:

func subscribe(DualOfEnumerator)

If we’re taking in the DualOfEnumerator, then shouldn’t the Disposable pop out?

Thus, here’s our final dual of everything:

protocol DualOfEnumerable {
    func subscribe(DualOfEnumerator) -> Disposable
}

protocol DualOfEnumerator {
    func enumeratorIsDone()
    func error(Error)    
    func next(Element)
}

By Any Other Name

Okay, one more time, this is what we have:

protocol DualOfEnumerable {
    func subscribe(DualOfEnumerator) -> Disposable
}

protocol DualOfEnumerator {
    func enumeratorIsDone()
    func error(Error)
    func next(Element)
}

Let’s massage these names a bit.

Starting with DualOfEnumerator, let’s use some slightly better function names, which indicate these things just happened:

protocol DualOfEnumerator {
    func onComplete()
    func onError(Error)
    func onNext(Element)
}

Cool, looking better and more consistent already.

How about these type names though? They’re straight garbage.

Let’s change them a bit.

  • The DualOfEnumerator is something that pays attention to what happens with a linear group of objects. You could say it observes the linear group.
  • The DualOfEnumerable is the subject of that attention. It’s the thing we’re observing; thus you could say it is observable.

With this in mind, let’s do some renaming:

protocol Observable {
    func subscribe(Observer) → Disposable
}

protocol Observer {
    func onComplete()
    func onError(Error)
    func onNext(Element)
}

Whoa 🤯

We just built the two foundational objects in RxSwift; you can see the real ones here and here.[1])

These two types are what drive the basis for RxSwift and reactive programming.

About Those “Fake” Protocols

The two “fake” protocols I described above aren’t really fake at all. In reality, there are analogous types in Swift:

y tho?

So why bother?

So much of modern development — particularly app development — is about being asynchronous. The user has unexpectedly tapped this button. The user has unexpectedly changed this segmented control. The user has unexpectedly selected this tab. This web socket just unexpectedly gave us new information. This download is unexpectedly, and finally, complete. This background task has just unexpectedly finished. The list goes on and on.

There are so many ways to handle these sorts of things in today’s CocoaTouch world:

  • Notifications
  • Callbacks
  • Key-Value Observation
  • Target/action

Imagine if all of those things could be reflected by one unified interface. An interface that can reflect pretty much any kind of asynchronous data or event in your entire app?

Now imagine there was an entire suite of functions that allow you to modify these streams, changing them from one type to another, extracting information from within the Elements, or even combining them with other streams?

Suddenly, there’s an entire new, universal toolbox at our disposal.

And lo, we’re back where we started:

An API for asynchronous programming with observable streams

That’s exactly what makes RxSwift so powerful. And, similarly, Combine as well.

What’s Next

If you’d like to see more about RxSwift in action, I encourage you to read my five-part blog series from late 2016. It covers the creation of the world’s dumbest CocoaTouch app, and then converts it step by step to RxSwift.

In one [or more] future post[s], I’ll cover why many of the techniques used in my RxSwift primer are not applicable to Combine, and compare and contrast Combine with RxSwift.


  1. Note in the case of Observer, the three on() functions are combined into one on(Event), where Event is an enum that specifies if the event is a completion, next, or error.