Saying hello to Combine framework (API Call & Parsing) — Part-2

Shail Patel
Mindful Engineering
9 min readDec 23, 2019
Photo by Benjamin Wong on Unsplash

Welcome back to this series of Combine framework. I hope the first part of this series clears the power of Combine in the development world.

In this second part, we will discuss Publisher, Subscribers, Operators, and Subjects briefly, and also see how to call API using Combine, and also discuss one very important topic called Diffable Datasource.

If you didn’t read the first part of this series then I highly recommend you to read the first part first, because the basics of Combine is described in the first part of this series. Saying hello to Combine framework — Part-1.

Publisher

We discussed the basics of Publisher in the first part, Now let’s check some of the in-built Publishers which combine provides.

Just

Just is a publisher that emits the output to all the subscribers once and then finish. A Just gives only one output. A Just publisher always produces a value, which means a Just publisher can’t fail with an error.

You can also use a Just publisher to replace the Catch statement for publishers. You can pass any value with a Just publisher, like Just(“example”).

Future

Future is a publisher that eventually produces a single value then finishes or fails. It’s ideal to use when you want to make a single request or get a single response.

You can see the example of Future publisher above. We are providing success and failure using promise. When you want multiple responses or multiple requests then I recommend you not to use Future publisher.

Published

Published is a type that publishes a property marked with an attribute. Publishing a property with the @Published attribute creates a publisher of this type.

You can access the publisher with the $ operator. We have already seen the use of @Published property in the first part.

Empty

Empty is a publisher that never publishes any values and optionally finishes immediately. You can create a ‘Never’ publisher which never produces the value and also never fails using Empty(completeImmediately: false).

Empty is useful when we want to return a publisher but don’t want to propagate any values. We can use Empty publisher like. let emptyPublisher = Empty<String, Never> .

There are some other publishers which are not much useful like Fail, Sequence, Deferred, ObservableObjectPublisher for this demo of API parsing. So that’s it for publishers. We will learn more about publishers in upcoming posts.

Operators

There are lots of operators in the Combine framework. We will discuss the ones which are important and used frequently.

combineLatest

combineLatest receives and combines the latest elements from two publishers. There are also available combineLatest3 and combineLatest4 for combining elements from three and four publishers.

You can mix different upstream publisher types into a single stream. We have seen the use of a combineLatest in the first part.

merge

merge receives and combines the latest elements from two publishers. It goes up to merge8. In all cases, the upstreams publishers are required to have the same output type, as well as the same failure type.

If you want to mix different upstream publisher types into a single stream, then you likely want to use either combineLatest or zip, depending on how you want the timing of values to be handled.

zip

zip receives and combines the latest elements from two publishers. There are also available zip3 and zip4 for combining elements from three and four publishers.

The only difference between combineLatest and zip is in cobineLatest if any of one publisher value changed the combineLatest perform actions, but zip performs an action if both the publisher value changes.

assertNoFailuer

A publisher that raises a fatal error upon receiving any failure, and otherwise republishes all received input. It is useful when you want to verify that no error occurs.

It will cause the program to terminate if the assert is violated. You can use this operator as below.

.assertNoFailure()
// OR
.assertNoFailure("What could possibly go wrong?")

retry

retry is an operator which is useful when any request fails and you want a case like if a request fails it will retry up to some number of tries.

You can use this operator like .retry(3) . In this, if the request fails it will try 3 times.

debounce

debounce collapses multiple values within a specified time window into a single value. debounce is useful in a case where you are filtering an array using request and you want the request to be sent if the user not entering some new data for some time like 0.5sec.

Using debounce you can reduce the number for the request. The operator takes a minimum of two parameters, an amount of time over which to debounce the signal and a scheduler on which to apply the operations.

There are many more operators that are there in the Combine framework, but some of them which are useful illustrated above.

Subjects

In the first part, we have seen the basics for Subjects in the Combine framework. Subjects are a special kind of Publisher that can insert values, passed from the outside, into the stream.

Subjects can be used to “inject” values into a stream, by calling .send() method. This is useful for integrating existing imperative code with Combine. A subject can also broadcast values to multiple subscribers.

If multiple subscribers are connected to a subject, it will fan out values to multiple subscribers when send() is invoked.

There are two types of subjects in the Combine framework CurrentValueSubject and PassthroughSubject. Both act like the same, but the main difference between both is CurrentValueSubject has the default value and PassthroughSubject does not have the default value.

When .send() method is called on CurrentValueSubject. It will send the value over the default value.

Subscribers

In the first part, we have seen the basics for Subscriber. In this part, we will see the in-build subscribers.

There are two subscribers named as assign and sink. assign applies values passed down from the publisher to an object defined by a keypath. The keypath is set when the pipeline is created. The example of assign is as follows.

.assign(to: \.isEnabled, on: signupButton)

sink accepts a closure that receives any resulting values from the publisher. The example of the sink is as follows.

.sink { receivedValue in     
print("The end result was \(String(describing: receivedValue))")
}

Most other subscribers are part of different Apple frameworks. For example, nearly every control in SwiftUI can act as a subscriber. The View protocol in SwiftUI defines an .onReceive(publisher) function to use views as a subscriber.

Diffable Datasource

This year in WWDC Apple introduced one very important UI update called Diffable Datasource. Diffable Datasource is available for both UITableView and UICollectionView.

So all you were think UITableView and UICollectionView is so much power so what is the need for Diffable Datasource. Sometimes we all get some crash because of the index and some update, using Diffable Datasource that all errors will not occur.

Using UITableViewDiffableDataSource or UICollectionViewDiffableDataSource along with NSDiffableDataSourceSnapshot you can create safe, animate changes between two states without having to keep track of which records were added, moved, or deleted.

Diffable Datasource calculates the difference between two snapshots and applies these changes with an appropriate animation. Before iOS 13, this required a significant amount of code and often times we would just give up and call reloadData.

We will see it’s implementation in our demo. First, let discuss some basic requirements for use Diffable Datasource. The data we are working with has to be Hashable to work with snapshots. We can create datasource and snapshot like below.

var datasource: UITableViewDiffableDataSource<Section, Data>!var snapshot: NSDiffableDataSourceSnapshot<Section, Data>!

You have to define the section and the Model which you want to pass and both must conform to Hashable protocol otherwise, you will encounter an error. That’s enough for Diffable Datasource for now, we will see more of in some time in this part with example.

Ok, enough of theory part lets start with an example.

Example

In this part, we will see the example of API calls using MVVM with Combine and Diffable Datasource for UITableView.

The API we will be using is available at “https://jsonplaceholder.typicode.com/users”. It is just a dummy API which sends back dummy results.

In this example, we are showing data coming from API into UITableView using Diffable Datasource.

Let’s start with the Service Manager class in which we will do the API calling functionality.

Ok, so let’s understand what we have done in the ServiceManager class.

  1. We have created an enum for API failure condition. Here I created only one case you can create any number of the case you want.
  2. Next, Let’s create a structure to parse the response from the API. Here we want to value and full response to pass. Here we use generic for value.
  3. Now the actual API service method we have created. Here we used decodable for decoding the response. We are returning the publisher of type AnyPublisher<Response<T>, Error> . After creating dataTaskPublisher we decode the result using tryMap. Using JSONDecoder we are decoding the value from the result. Finally, we are returning value and response.

Now let’s move to our model class.

What we just did with our model class?

  1. We are creating the model using structure confirming the Decodable and Hashable protocol.
  2. Here we are confirming Decodable protocol because we are using JsonDecoder for decoding the values.
  3. Here we are confirming Hashable protocol because we want to use the DiffableDatasource. As discussed earlier model must be Hashable to use DiffableDatasource.

We have created the ServiceManager class and our model class now we have to create one user service class from which we are calling all API for the user class.

We are using MVVM pattern so for service we do not directly call APIs from the user class instead we are calling the APIs from UserService class. In UserService class we will fill the model.

What we just did?

  1. Here we are creating one method which returns AnyPublisher<[UserModel], Error> . And calls the API from ServiceManager class.
  2. Using .map(\.value) we are filling the values in the model.

Now when we have created the UserService class to call API, from where we are calling API from UserService class? So the answer is pretty simple ViewModel class. So for that, we are creating a class named UserViewModel.

What we just did?

  1. Here we created one array for storing the values and filter the search result locally and created one publisher using CurrentValueSubject for passing the changes to the controller.
  2. Using UserService class reference we are calling API.
  3. Using sink subscriber, we are processing the result. We store the result in an array as well as pass the value in the CurrentValueSubject publisher using send .
  4. And lastly, we are creating one function in which we filter array locally when the user searches anything and sends updated data.

Now let’s start with the Controller class.

What we just did?

  1. First, we created one enum for Section which we will use in DiffableDatasource.
  2. Then we created datasource instance using UITableViewDiffableDataSource.
  3. In the initialization method, we have setup datasource, Searchfield UI, getting user data from ViewModel, and according to that set the snapshot for DiffableDatasource.
  4. Now let’s understand how we can set up the datasource for DiffableDatasource. So first we create a new datasource using UITableViewDiffableDataSource<Section, UserModel> . This is the section that we have created earlier. Here the notable point is that enum is by default Hashable so we don't need to use a Hashable protocol for that. After giving the table instance we get three values in Closure for a table, indexPath, and data. After that, we are creating table cell instance like before and assign value to that cell. So that’s it for setting DiffableDatasource for UITableView.
  5. Now let’s discuss snapshot creation. First, we are creating a new snapshot using NSDiffableDataSourceSnapshot<Section, UserModel>() . Then we are assigning a section to this snapShot using snapshot.appendSections([.main]) , then we assign data to snapShot using snapshot.appendItems(users) . Finally, we apply this snapShot to datasource which we have created earlier using dataSource.apply(snapshot, animatingDifferences: true) . And you just created one snapshot.
  6. Now let’s come back to get data from ViewModel. We are calling the method which we have created in ViewModel for getting data.
  7. After getting data from ViewModel we use .sink to get the data and assign that data to created snapShot, and by doing this the data is filled in a table and you can see nice animation for inserting data into the table.
  8. If you are thinking that you don’t have to use the Delegate method for a table because you used DiffableDatasource then you are wrong. Like you have to use didSelectRowAt method for a table using the Delegate. You can get the exact data for index using dataSource.itemIdentifier(for: indexPath) .

That’s it, you just created one API parsing demo with MVVM using DiffableDatasource.

Conclusion

In this second part of the Combine framework post, I’ve demonstrated how you can use Combine for parsing API using MVVM.

I hope now the abilities of DiffableDatasource are very clear to you. The best thing about DiffableDatasource is we don’t have to bother for index related issues and for animation.

In the next part, we will discuss how we can use combine with SwiftUI.

Meanwhile, you can find full source code from the following GitHub Project.

Stay tuned with Mindful Engineering, we will be posting more detailed posts regarding Combine and such other interesting stuff in upcoming weeks.

--

--