Custom Combine operator ~ practical use case

In this article I will show you how I learned to write my own custom Combine operator as part of my Combine journey of learning and understanding.

Arie Peretz
4 min readMay 9, 2021

Preliminary knowledge

Motivation

My struggles against learning Apple’s Combine framework on my own I thought it will be a nice way of learning by trying to build my own operator.

So when I stumbled across a common use case that I can try building an operator for, I challenged myself to do so.

The common use case in hand

Let’s say for example that you have an api service that returns an array of objects (object A) and this Codable object is full of data that we don’t really want to use, perhaps we just want to display on the screen a part of that data or even more than that, we would like to manipulate the data and format it in some way so it suites our view perfectly.

In order to do that we will need a second type of object (object B) and do some sort of mapping between object A and object B (transform object A to object B) so that we get an array of type object B.

So, if we have a publisher of array of type object A, our mission is to transform every item in the array into object B and end up with a publisher of array of type object B.

Visualization of our mission

So how we can do that you ask? great question. Let’s find out :)

Preparing the playground

NOTE: Feel free to open up playground and start experimenting yourself.

For the sake of simplicity let’s use the following structs:

Now, let’s create a simple users list of type UserDataModel:

Before we continue, let’s create a computed variable inside UserDataModel struct that will allow us easily convert a UserDataModel object into a User object like this:

Direct approach

Recalling our mission here, mapping the objects can be achieved by using the Combine’s map operator (outer map) to convert it to another list of objects and using Swift standard library’s map function (inner map) to iterate over the list and convert each UserDataModel element to User.

While this approach works fine, it does not read very well and it can be pretty confusing to use.

Sophisticated approach

Let’s see how we can take things one step further and create an extension on Publisher and use some pretty basic operators on that publisher to achieve the same result but in a way nicer way.

In simple words, we will do the folowing steps:

  1. Take the given array of UserDataModel objects (upstream value) and break it down to separated UserDataModel values.
  2. Convert each of the elements into a User object.
  3. Assemble back an array of User objects (downstream value).

Quick exercise: Try to guess what are the actual operators described above.

Custom Combine Operator: MapEach

Let’s watch the magic written in code:

NOTE: These two functions do the same with different parameters (closure and keypath).

We extend Publisher only where the Output type conforms to Sequence and Failure is Never because this is actually our use case. These constraints help us along the way. Also, the downstream array’s object type is generic.

First, we apply the flatMap operator to transform the array from the upstream publisher into a new publisher, in this case, a publisher of sequence.

Next, we apply the map operator on each element, either using a closure or a keypath in order to get the converted elements stream.

Finally, we apply the collect operator to assemble all the elements back together into an array.

Let’s see it in action:

Important update:

The implementation of the mapEach operator as explained above is working nicely but there is one drawback that we must take into account here.

In this solution I we use the collect operator that works as described in the documentation:

“Collects all received elements, and emits a single array of the collection when the upstream publisher finishes.”

In other words, mapEach works only for one value (array of objects) and that’s it.

In order to accomplish receiving multiple values and mapping them we need to change the mapEach operator a little bit, like this:

In the above solution we use 2 different map functions: Combine’s map and Swift language’s map.

The first map is used to map the sequence and the second map is used to mapping each element in the sequence. Awesome!

Now we can use our mapEach operator to receive infinite amount of values (arrays).

Conclusion

I hope this article has shed some light on how you can build your own Combine operators and that it will give you the strength to continue exploring this amazing framework.

I would like to take this opportunity to thank Ruslan Sharaev¹ for giving me the idea of that kind of use case which I simply took as a challenge to make from it a useful operator that we can all use.

[1]: Ruslan Sharaev: 4 Combine and Swift UI Example App Swift Issues (https://www.youtube.com/watch?v=e8TA6hc6AFI)

--

--