Combine: Implement Search

Photo by Clem Onojeghuo on Unsplash

It has been a while now Since Apple came up with its own FRP framework Combine. Wait for what ??? FRP What is that ….

Functional Reactive Paradigm — Yes!!! That’s what we say to a paradigm where instead of focusing on the task we focus on asynchronous streams of values.

Before I dive in to explain the implementation of search with the combine, let us first understand a little about Combine Framework.

Combine is Swift declarative framework for processing values over time [1]. It imposes functional reactive paradigm of programming, which is different from the object-oriented one that prevails in iOS development community.

Combine is a lot to explain but let’s see what is needed to make a combine pipeline. Yes that’s what we call to any combine flow — “pipeline”.

Publisher → Operator → Subscriber

That’s what we need to create any pipeline.

let’s learn each of these while building our Search.

Before we start writing the code first think what we usually do If we are to implement the search feature in our app with the imperative style.

Skip two paragraphs if you want to jump on the implementation.

Many would agree that we usually start with taking a SubClassed UITextField (Search Field — rest of the article will say Search Field) or a UISearchView. Adding it to our View and further, we add target function or few may reach to add a notification observer or a closure for our SearchField to observe the change in its text value. I really don’t care till now whatever way you find to make your UI look similar to what our client would want us to make it look like.

The main part of search implementation goes where we keep typing and we make calls to a Web service to fetch the search result. As in iOS developer we must have implemented Web Service asynchronously and passing in the closure or putting some observer pattern to bind the data back to our representation UI. But there is a catch where we all have to implement some fuzzy logic or make use of Operations to wrap our API call so that we bind the result only from the latest API call requested.

Okay, That was a lot of theory, let us implement the Search with Combine.

We added a UITextField to our View Controller, same what we do as usual but now the story is going to change from here.

We have declared a var textPublisher of type AnyPublisher<String, Never>?, here AnyPublisher<String, Never>? is a type erasure that we get making use of .eraseToAnyPublisher.

Refer line number 40 where I create the textPublisher, this publisher is responsible for publishing the event on change of text in SearchField. So the first part of our pipeline is created.

Now, We will need a Subscriber to listen to any value published by the above-created publisher.

Refer to line 45 — let textSubscriber = Subscribers.Assign(object: self, keyPath: \.text)

We are creating the subscriber which will assign the value published from our textPublisher to the text property on the self-object.

After the subscriber is in place let us make the subscription complete by making our subscriber subscribe to our publisher. check line — 47.

By now we will be able to observe SearchField for any change in its text value.

On line 24 we declared a text property that will be set on any published value captured by our textSubcriber.

That was pretty good to have implemented the UITextField observation using combine. But wait there is a missing part as explained earlier about the Combine Pipeline.

Publisher → Operator → Subscriber was shown as the parts of combine. But operator is missing from what we have implemented till now. It just because operators are not a mandatory part. They only required if we want to transform the value published from publisher to some value expected by the subscriber.

Let us see this simple example to understand.

Break above example in Publisher, Operators, and Subscriber.

Publisher — var number is a Sequence Publisher which publishes the value from 1 to 20 on being asked by a subscriber.

Subscriber — if we see our subscriber .sink {} it requests the value and prints it.

Operator — But If we want only the even values to be printed it will require making use of operators that will filter out the odd values. And the followed by .collect() operator collects the filtered values in an array. That will be passed to the subscriber as an array of even values.

We just saw the use of operators in transforming values and being part of Combine pipeline. That would have given a better understanding of the use of operators in combine pipeline.

Let’s head back to our sample search implementation.

Now move to another part of our example to fetch results from a remote source (we will fake it here in our example) and using the latest search API call result to show in our UI.

Refer to line 49. We make use of a new type name PassthroughSubject, But wait.. What is the subject ?

Subject is a special kind of Publisher that can insert values, passed from the outside, into the stream.

This subject publisher here is of a Void type that never fails

var search: PassthroughSubject<Void, Never>

It publishes a void value as there is a change in the text property created on our controller.

Now we will see the role of operators, how we used the map operator to get the publishers returned from func search(keyword: ) and as we keep firing the search call from every change in text there would as many web-service hit to search relevant data. When requests are fired in a short period of time, their responses may arrive out-of-order. However, we are almost always interested in the latest network request.

There comes another operator to rescue — switchToLatest().

We switch the stream to the latest publisher, i.e. AnyPublisher<[String], Never> using switchToLatest()

Check line 54. this line shows how switchToLatest() operator make our subscriber accept value only from the latest API call publisher.

For the case of demonstration, I am only printing values to the console, where we can see that for every change in SearchField we receive a publisher but result is only printed for the latest publisher. Check line 54, print() function to debug the publisher.

I will end here with a lot to improve and may be very soon would write another article on using combine to bind data to list.

Please drop in comment if any questions or suggestions.