Persist Business Logic With Swift Combine

Data-driven Combine

Kevin Cheng
Dec 31, 2019 · 6 min read

In the previous series, we successfully built this platform on top of SwiftUI, where you can freely observe the sequences of values flowing through the Combine publisher.

We’ve also created examples demonstrating a handful of default Combine operators that are capable of changing and transforming values within sequences, such as filter, map, drop, and scan. Moreover, we introduced a couple of more operators that join (Zip and CombineLatest) or unify (Merge and Append) sequences.

At this point, some of you might be a little sick of having to manage or maintain so much code for each of the examples (at least I am). See how many of them are in this combine-magic-swiftui repo under the tutorial folder? Each of the examples are SwiftUI views. Each one simply feeds one or a few publishers to the StreamView, and StreamView subscribes the publishers when the subscribe button is tapped.

So I should be able to programmatically generate a list of publishers on the app’s start and reuse StreamView, like in the screenshot below.

However, the problem with this solution is scalability when there are a lot of publishers to create.

My solution to this issue is I have to somehow persist these publishers. If I can somehow serialize these publishers, I’ll be able to persist them. If I manage to persist them, not only will I be able to modify them without changing code, I’ll also be able to share them with other devices that support Combine.


Persist and Transfer Combine Operators

Now, let’s revisit our goals here more specifically. Since we have a list of streams and operators in the Publisher format, we’d like to be able to persist them in any kind of storage — like a hard drive or database.

Obviously, we’d also need to be able to convert the stored data back to the publisher, but moreover, we want to be able to share, transfer, and distribute these publishers with operators from one place to another.

Once we’ve set up this kind of structure, as you’ve probably already pictured, in a distributed environment, a centralized service can start driving computing logics for a bunch of clients.


Codable Structure

How do we do this? We start with designing a structure that’s serializable and deserializable. Swift’s Codable protocol allows us to do that through JSONEncoder and JSONDecoder. What’s more, the structure needs to properly represent data and behaviors for the smallest unit of a value within a stream up to a complex chains of operators.

Before we move forward, in order to understand what components are needed for the structures we’re about to create, let’s recap a basic stream we’ve built from the previous series.


Stream of Numbers

This is the most straightforward stream; however, if you look deeper you’ll observe this isn’t just a simple sequence of an array. Each of the circular boxes has its own delay operator that drives the actual timing when it should be emitted. Each value in Combine looks like:

And the entire thing looks like:

Each value delays a second, and the next value has the same delay operator appended.

Therefore, we learn two things here from observations.

  1. The stream isn’t the smallest unit in the structure. The stream value is.
  2. Each stream value can have unlimited operators that manipulate what and when a value is being emitted.

Create Your StreamItem

Since the stream value and its operators are the smallest unit, let’s start with creating its structure. Let’s call it StreamItem.

StreamItem includes a stream value and an array of operators. As per our requirements, we want to be able to persist everything in the structure so both value and StreamItem conform to the Codable protocol.

The stream value needs to be generic in order to accommodate values in any type.


Create Your StreamModel

We’ll discuss the structure for operators later. Let’s put a StreamItem array together as StreamModel.

StreamModel holds an array of StreamItem(s). StreamModel also has an ID, name, and description properties for identifying and descriptive purposes. Again, everything within StreamModel has to be Codable for persistence and distribution.


Create Operator Structure

As we mentioned earlier, delay operators can change the StreamItem’s emission time.

We treat the delay operator as an enum with one associated value to persist the delay time.

Certainly, the perator enum also needs to conform to Codable, which includes encoding and decoding the underline associate values. See the complete implementation below.

Now, we have a good structure to represent this serial stream that emits values from 1 to 4 with a second delay interval.


Convert the StreamModel to Publisher

Now we have built a stream instance; however, if we don’t convert it to the publisher, everything so far will have been meaningless. Let’s give it a try.

First of all, each operator model links to an actual Combine operator that should add to a given publisher and return the operated publisher.

There is only one type of operator, delay, for now. We’ll add more as we go.

Now we can start applying publishers to each StreamItem.

We start with a Just value, generalize it with the eraseToAnyPublisher method, and then apply publishers from all the associated operators.

On the StreamModel level, this is how we get the entire stream’s publisher.

You guessed right: We use the append method to concatenate publishers together.


Visualize Stream, Edit, and Visualize Again

Now, we can simply decode the publisher and feed and construct the StreamView (see how we did that in the previous series). Last but not least, we now can simply edit StreamModel, add more StreamItem(s) with new values, and even share this model with other devices over the internet.

See the demo below. We’re now able to make changes of the stream without changing code.


Next Chapter: Serialize/Deserialize Filter and Map Operators

In the next piece, we’re going to add more operators in the enum operator and start applying them on a stream level.

Until next time, you can find the source code here in this combine-magic-swifui repo under the combine-playground folder.

Better Programming

Advice for programmers.

Kevin Cheng

Written by

iOS engineer / entrepreneur / freelancer / San Francisco

Better Programming

Advice for programmers.

More From Medium

More from Better Programming

More from Better Programming

More from Better Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade