6 Swift Combine Operators You Should Know
In this article, we’ll look at six useful Combine operators. We’ll do this with examples, experimenting with each one in the Xcode Playground.
The source code is available at the end of the article.
Well, without further ado, let’s get started.
1.prepend
This group of statements allows us to prepend (literally “prepend”) events, values, or other publishers to our original publisher:
import Foundation
import Combine
var subscriptions = Set()
func prependOutputExample() {
let stringPublisher = ["World!"].publisher
stringPublisher
.prepend("Hello")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
Result: Hello
and World
! are output in sequential order:
Now let’s add another publisher of the same type:
func prependPublisherExample() {
let subject = PassthroughSubject()
let stringPublisher = ["Break things!"].publisher
stringPublisher
.prepend(subject)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
subject.send("Run code")
subject.send(completion: .finished)
}
The result is the same as the previous one (note that we need to send an event .finished
in subject so that the operator .prepend
have worked):
2. append
Operator .append
(literally “add to end”) works similarly .prepend
, but in this case, we add the values to the original publisher:
func appendOutputExample() {
let stringPublisher = ["Hello"].publisher
stringPublisher
.append("World!")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
As a result, we see Hello
and World
! outputted to the console:
Similar to how we previously used .prepend
to add another Publisher
a, we also have such an opportunity for the operator .append
:
3.switchToLatest
More complex operator .switchToLatest
allows us to combine a series of publishers into one stream of events:
func switchToLatestExample() {
let stringSubject1 = PassthroughSubject()
let stringSubject2 = PassthroughSubject()
let stringSubject3 = PassthroughSubject()
let subjects = PassthroughSubject, Never>()
subjects
.switchToLatest()
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
subjects.send(stringSubject1)
stringSubject1.send("A")
subjects.send(stringSubject2)
stringSubject1.send("B") // отброшено
stringSubject2.send("C")
stringSubject2.send("D")
subjects.send(stringSubject3)
stringSubject2.send("E") // отброшено
stringSubject2.send("F") // отброшено
stringSubject3.send("G")
stringSubject3.send(completion: .finished)
}
Here’s what’s going on in the code:
- We create three objects
PassthroughSubject
to which we will send values. - We create the main object
PassthroughSubject
which dispatches other objectsPassthroughSubject
… - We send
stringSubject1
on the main subject. stringSubject1
gets the value A.- We send
stringSubject2
on the main subject, automatically discarding stringSubject1 events. - Likewise, we send values to
stringSubject2
, connect tostringSubject3
and send it a completion event.
As a result, we see the output A
, C
, D
and G
:
For simplicity, the function isAvailable
returns a random value Bool
after some delay.
func switchToLatestExample2() {
func isAvailable(query: String) -> Future {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
promise(.success(Bool.random()))
}
}
}
let searchSubject = PassthroughSubject()
searchSubject
.print("subject")
.map { isAvailable(query: $0) }
.print("search")
.switchToLatest()
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
searchSubject.send("Query 1")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
searchSubject.send( "Query 2")
}
}
Thanks to the operator .switchToLatest
we achieve what we want. Only one Bool value will be displayed:
4.merge (with 🙂
We use .merge(with:)
to combine two Publishers
s, as if we were getting values from only one:
func mergeWithExample() {
let stringSubject1 = PassthroughSubject()
let stringSubject2 = PassthroughSubject()
stringSubject1
.merge(with: stringSubject2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
stringSubject1.send("A")
stringSubject2.send("B")
stringSubject2.send("C")
stringSubject1.send("D")
}
The result is an alternating sequence of elements:
5.combineLatest
Operator .combineLatest
publishes a tuple containing the latest value for each publisher.
To illustrate this, consider the following real-world example: we have a username, password UITextFields
and a continue button. We want to keep the button disabled until the username is at least five characters long and the password is at least eight characters. We can easily achieve this using the operator .combineLatest
:
func combineLatestExample() {
let usernameTextField = CurrentValueSubject("")
let passwordTextField = CurrentValueSubject("")
let isButtonEnabled = CurrentValueSubject(false)
usernameTextField
.combineLatest(passwordTextField)
.handleEvents(receiveOutput: { (username, password) in
print("Username: (username), password: (password)")
let isSatisfied = username.count >= 5 && password.count >= 8
isButtonEnabled.send(isSatisfied)
})
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
isButtonEnabled
.sink { print("isButtonEnabled: ($0)") }
.store(in: &subscriptions)
usernameTextField.send("user")
usernameTextField.send("user12")
passwordTextField.send("12")
passwordTextField.send("12345678")
}
After usernameTextField
and passwordTextField
will receive user12
and 12345678
accordingly, the condition is satisfied and the button is activated:
6.zip
Operator .zip
delivers a pair of matching values from each publisher. Let’s say we want to determine if both publishers have published the same value Int
:
func zipExample() {
let intSubject1 = PassthroughSubject()
let intSubject2 = PassthroughSubject()
let foundIdenticalPairSubject = PassthroughSubject()
intSubject1
.zip(intSubject2)
.handleEvents(receiveOutput: { (value1, value2) in
print("value1: (value1), value2: (value2)")
let isIdentical = value1 == value2
foundIdenticalPairSubject.send(isIdentical)
})
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
foundIdenticalPairSubject
.sink(receiveValue: { print("is identical: ($0)") })
.store(in: &subscriptions)
intSubject1.send(0)
intSubject1.send(1)
intSubject2.send(4)
intSubject1.send(6)
intSubject2.send(1)
intSubject2.send(7)
intSubject2.send(9) // Не отображено, потому что его пара еще не отправлена
}
We have the following corresponding values from intSubject1
and intSubject2
:
- 0 and 4
- 1 and 1
- 6 and 7
Latest value 9
is not displayed because intSubject1
haven’t posted the corresponding value yet:
Resources
Source code is available at Gist…
Conclusion
Interested in other types of Combine operators? Feel free to visit my other articles: