Combine - Processing Values with Operators in Swift

In this article, we will explore some of the operators provided in Combine.

Before that, If you haven’t read, Combine Framework Beginner Tutorial in Swift, Click Here.

Combine framework comes with a ton of operators to process the values received by the publisher.

We will look into some of these operators one by one.

Let’s Start

If you have read my previous article about Combine, you must already know how map(_:) and filter(_:) works. So let’s start with debounce(for:scheduler:options:), this operator is used to make sure you receive values only after a specific time, where you specify the time.

let name = Notification.Name("com.publisher.combine")

let cancellable = NotificationCenter.default
    .publisher(for: name)
    .map({ ($0.userInfo!["SearchString"] as! String) }) // Returning String From Notification
    .filter({ $0.rangeOfCharacter(from: CharacterSet.decimalDigits) == nil }) // Filter values containing numbers
    .debounce(for: .seconds(1), scheduler: DispatchQueue.global()) // Allowing one value in a second
    .sink { value in
        print(value)
}

NotificationCenter.default.post(name: name, object: nil, userInfo: ["SearchString": "testOne"])

NotificationCenter.default.post(name: name, object: nil, userInfo: ["SearchString": "testTwo"])

NotificationCenter.default.post(name: name, object: nil, userInfo: ["SearchString": "testThree"])

/*
 Output:
 testThree
 */

We have posted three notifications, and only one output gets printed in the debug area, as there was a debounce set for one second. So, only the last notification went through.

Using operators is powerful, easy and readable because of declarative Swift API.

Next, let’s look into .receive(on:options). It is essential to use this operator when you want to specify the thread you want to execute your code in, let’s look into an example for a proper demonstration.

let name = Notification.Name("com.publisher.combine")

let cancellable = NotificationCenter.default
    .publisher(for: name)
    .map({ ($0.userInfo!["SearchString"] as! String) }) // Returning String From Notification
    .filter({ $0.rangeOfCharacter(from: CharacterSet.decimalDigits) == nil }) // Filter values containing numbers
    .debounce(for: .seconds(1), scheduler: DispatchQueue.global()) // Allowing one value in a second
    .receive(on: DispatchQueue.main) // Executing the .sink on the main thread
    .sink { value in
        self.textField.text = value
}

NotificationCenter.default.post(name: name, object: nil, userInfo: ["SearchString": "testOne"])

In the code shown above, receive(on:) is executed on the main thread to update the value of textField. Instead of using another block which is DispatchQueue.main.async { … }, we can simply ask the receive(on:) to execute our .sink { … } task on the main thread.

There are times we need to decode some JSON data into a custom Codable object. Publisher is having another simple operator for this, decode(type:decoder:), and How do we use this?

.decode(type: MyObject.self, decoder: JSONDecoder())

Just like the other operators, we can use this operator to decode our objects on the go.

Now, let’s look into the final demonstration of this tutorial, which I think is the most useful and the important one. There are times we want to assign the values directly to the objects, and for that, we don’t want to execute another block, so Publisher has a special function to assign values directly to our objects, and the function is named assign(to:on:). We’ll try to set the text of our existing example but without the sink(receiveValue:) function.

Let’s do this.

let name = Notification.Name("com.publisher.combine")

let cancellable = NotificationCenter.default
    .publisher(for: name)
    .map({ ($0.userInfo!["SearchString"] as! String) }) // Returning String From Notification
    .receive(on: DispatchQueue.main) // Requesting to execute the suscriber task on main thread
    .assign(to: \.text, on: textField) // Updating the value of textField

NotificationCenter.default.post(name: name, object: nil, userInfo: ["SearchString": "testOne"])

Note: If you run this code in playground, the code will fail, as the textField here is imaginary.

The above demonstration shows us how we can set the value of an object without executing any block, just with help of keyPath and assign(to:on:) function, we can achieve this. This functionality is not restricted to updating UI, we can do so much more, like updating your model, wherever you can use the keypath, just try wherever you can.

I hope I was able to demonstrate the use of operators in Combine, and there’s more to Combine, so subscribe if you haven’t, to stay updated about my upcoming articles.

If you have any suggestions or questions, Feel free to connect me on Twitter or Reddit. 😉

To read, Combine - Creating your own Publisher with @Published, Click Here.

Combine - Creating your own Publisher with @Published

Combine Framework Beginner Tutorial in Swift - Working with NotificationCenter