Using Combine's MergeMany to fulfill your requests
Many men and women, have struggle to keep their sanity, while working with asyncronous programming, most have wondered beyond reactiveness, and many have failed, many times. The part the succeded, and now lives in the reactive world, perhaps are scared and don't fully understand how the pillars of reactive programming work. And the fallen, but brave developers who were demolished by complexity and framework adoption, find themselves deep in a callback hell.
Sorry about the dramatic introduction, but it was necessary, reactive programming isn't a simple subject and many people, myself included, have struggled with the concept and with frameworks that supports it.
If you felt a bit scared, or doesn't know what I'm talking about, I strongly recommend you to read the following article, to get familiar with subject and understand the problem I'm trying to solve. These articles helped me (a lot) to understand and use this concept in projects that I've worked on, helped to a point that I felt comfortable to write this post to help other fellow developers.
You doesn't need to be an experienced developer, to know that in some time you'll need to do some work with asynchronous programming, but most cases don't get too complex, but when they do, oh boy, things can get ugly. Imagine have to work with multiple, asynchronous network requests, that in the end you want to be part of the same result.
There are many aways to achieve this behavior, you can find a variety of third party frameworks that solve this issue, until now, my go to option was to use DispatchGroup, entering and leaving the group and when all ended
group.notify is called and you can do some work with your data.
But as June of 2019, apple introduced the Combine framework, as apple describe it's, combine is framework for handling of asynchronous events by combining event-processing operators.
The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.
Alright, so from here I'm assuming, you know your way around iOS development, have worked with some remote API, and is familiar with some reactive concepts, if you don't, just tag along, it will be quite a ride 😎.
First create a new iOS application project, add a simple UITableView to your main UIViewController and register some UITableViewCell that is able to display some information, like an image, title and subtitle.
We’re going to use the awesome, open and free Rick and Morty API.
For now we’re only going to use the
character/ endpoint, to retrieve a list of characters and later more info about that character.
Let's begin, start by adding the combine import in your view controller file.
Now let's do some work to fetch some characters, first we need to map the API responses, convert JSON to a struct that we can use in code. In some cases the API returns the result of the request wrapped in a result object, the result may vary from a single object or an array of objects. So let's create an abstraction using generics that can handle both cases.
The struct above will handle API results as long as the type T conforms with the Codable protocol, and we will create a Rick and Morty character representation just for that. By taking a quick look at the API's documentation I was able to identify some attributes that compose a character, so you don't have to do so 😚 .
OK, now that we got the object mapping sorted, let dive into some Combine code. First we will create a URLRequest, this part requires some boilerplate code that most of us are familiar with, but to summarize, we need to create a JSONDecoder, to decode JSON string into object or structs, "duh!", a URL containing the endpoint that will return the list of characters, the request object itself and finally set the HttpMethod to be used, which in our case is GET, because we want to GET some things.
After all this preparation, here it comes the new stuff, if you are one of the real OG developers, you know that, to make requests using the URLSession, you will need a dataTask with request and completion params, now with the Combine framework, the implementation of URLSession has a dataTaskPublisher, that is used in the same context but it now publishes things, in other words, the data you are requesting will now be published when done.
let session = URLSession.shared.dataTaskPublisher(for: request)
That’s cool and all but we don't work with plain data objects, we need to map this data into something that we can work on, that's were the combine framework and it's operators comes into play.
Let's break these down.
tryMaphere we can work with the network response, check for errors, headers etc, but as our main goal is simple, we're just returning data down the chain. Keep in mind that if the request fails none of the operator below will be called, you can handle any level of errors with the
decodeif everything went well with the request,
decodewill receive a data object, and that is just what our JSONDecoder need to turn JSON data into objects. We just need to pass the type of object that we need to decode from and the decoder that we want to use, remember the boilerplate code? Yep the decoder is up there, with our
convertFromSnakeCaseconfiguration. Nice 🐍
mapif you don't suffer from short memory (as I do), you don't need to scroll up to see that, our lovely API returns, all characters data inside a results object, and for that we created an
RickAndMortyResponseWrapper, but weren't expecting this object, we only working with a array of
Characters, this is what
mapif here for, we're mapping the result into results, witch are the array of characters.
receive(on:)this indicates that we want our data in the main thread as we're reloading our
tableView, by default the
dataTaskPublisherwork is done in a background thread.
sinkthe one that will join us all, this operation as apple describes in their docs “Attaches a subscriber with closure-based behavior.”, will trigger the request itself, this mean that someone is waiting to receive the data from the publisher. Here we can do some safe work with the characters and reload our tableView in the main, as we specified in the step above.
Now, besides the configuration to make the table view work, everything is ready to run. You can go ahead and see you application working with combine e be happy about it, but that's only the beginning, there's plenty more to come — deep sight — .
But what if we had many requests?
The next step is to display all episodes each character has appeared in, but our API only return de URL for these episodes, and is our job to fetch every single one of them to get more information to display. Before Combine, you would have to do some work to get every episode together, maybe use a
DispatchGroup to get all in one place, creating a lot of boilerplate and some error prone code, but that's not cool and now were cool 😎. Apple provided a structure called MergeMany, as may have guessed, it merges many Publishers.
Now what we have to do is, create a function that returns a publisher given an episode URL.
That's pretty much the same as before, except I introduced a
BANG! in the URL just to make this example easier, since the function now expects a return type of
With everything in place, we can create a function to fetch all episodes.
First we make sure the selected character has the episode URL array, then we use the
compactMap operator to create an array of publishers for each URL using the function we created before, and finally the
MergeMany structure itself, and all we need to do is serve the publisher array that we created and the structure will handle the rest. As you can see I'm using again
compactMapto map every publisher response into a
Episode and in the sink callback, appending the current episode into my episodes array and updating the tableView for every result just because it looks nice, check it out!
Now that you know these structures exists go ahead and start playing with them, get comfortable and maybe introduce it into your projects. I tried to cover a lot of subjects and I hope, I could spark a little of curiosity in your day.
The source code used in this example can be found in my GitHub, this repository will be in constant change, as I plan to use it in future posts, exploring Combine, theming this application for Dark Mode and any iOS related subject.
This exact example can be found in the
feature/combine-merge-many branch in GitHub. That is it, for now…