Leave a rating/review
Let’s take a look at the switchToLatest operator. First, define 3 PassthroughSubject publishers inside an example(of) stub - these will be the publishers that our main publisher - called publishers - will emit. Note that publishers’ value type is PassthroughSubject<Int, Never>, which means it will emit publishers as its value type.
example(of: "switchToLatest") {
// 1
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
let publisher3 = PassthroughSubject<Int, Never>()
// 2
let publishers = PassthroughSubject<PassthroughSubject<Int, Never>, Never>()
Then build the usual publisher-operator-sink-store chain, as you have done throughout the examples in this course. The operator in this case is switchToLatest().
// 3
publishers
.switchToLatest()
.sink(receiveCompletion: { _ in print("Completed!") },
receiveValue: { print($0) })
.store(in: &subscriptions)
If we run the playground now, nothing happens - we haven’t sent any publishers into our publisher. Send publisher1 to publishers and then send values 1 and 2 to publisher1
// 4
publishers.send(publisher1)
publisher1.send(1)
publisher1.send(2)
Now switch publishers. Send publisher2 to publishers, and then send 3 more values - the value 3 on publisher 1, and the values 4 and 5 on publisher 2
// 5
publishers.send(publisher2)
publisher1.send(3)
publisher2.send(4)
publisher2.send(5)
When we switch to publisher2, the active publisher - publisher1 - has its subscription cancelled. What do you think will happen when we try to send the value 3 to publisher 1?
Finally, send publisher3 to publishers, send another value on publisher2, and the rest of our sequence on publisher3.
// 6
publishers.send(publisher3)
publisher2.send(6)
publisher3.send(7)
publisher3.send(8)
publisher3.send(9)
Again, after the switch, publisher 2 has its subscription cancelled. Don’t forget to send completion events to the last publishers to be active - publisher3 and publishers.
// 7
publisher3.send(completion: .finished)
publishers.send(completion: .finished)
}
Run the playground. You’ll see that the sequence of values written to console is 1, 2, 4, 5, 7, 8, 9. As expected, any values sent to the old publisher after a switch don’t get emitted to the downstream consumer.
Now let’s take a look at the merge operator. Again, make an example(of) stub and create 2 publishers, both PassthroughSubjects, that take in Integer value types. Set up the usual publisher-operator-sink set of code, with publisher1 as the main publisher, and the merge operator taking in publisher2 as its argument.
example(of: "merge(with:)") {
// 1
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
// 2
publisher1
.merge(with: publisher2)
.sink(receiveCompletion: { _ in print("Completed") },
receiveValue: { print($0) })
.store(in: &subscriptions)
Since these are PassthroughSubject publishers we send the values in one by one. Bounce back and forth, sending values to publisher1 and publisher2, and send completion events to both publishers when you’re done.
// 3
publisher1.send(1)
publisher1.send(2)
publisher2.send(3)
publisher1.send(4)
publisher2.send(5)
// 4
publisher1.send(completion: .finished)
publisher2.send(completion: .finished)
}
Run the playground. The sequence printed to the console is in the same order we entered it - 1, 2, 3, 4, 5 - even though we did so with 2 different publishers!
Let’s look now at combineLatest. Remember that combineLatest can combine publishers with different value types. In an example(of) stub, define 2 publishers - one that emits Ints, and another that emits Strings, and build our example pipeline using publisher1 as the source publisher and use combineLatest with publisher2 as its argument.
example(of: "combineLatest") {
// 1
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()
// 2
publisher1
.combineLatest(publisher2)
.sink(receiveCompletion: { _ in print("Completed") },
receiveValue: { print("P1: \($0), P2: \($1)") })
.store(in: &subscriptions)
Again, bounce back and forth, sending integers to publisher1, and strings to publisher2, as well as completion events for both when you’re done.
// 3
publisher1.send(1)
publisher1.send(2)
publisher2.send("a")
publisher2.send("b")
publisher1.send(3)
publisher2.send("c")
// 4
publisher1.send(completion: .finished)
publisher2.send(completion: .finished)
}
Run the example. Recall that combineLatest only emits tuples of the latest values from each publisher when each publisher has emitted at least one value, so the first tuple emitted is (2,a), followed by (2,b), then (3, b), and finally (3,c)
Finally, let’s look at zip, which is like combineLatest, but combines values based on their index in each publisher. Again, setup 2 publishers and the example pipeline like you did with combineLatest but use the zip operator instead.
example(of: "zip") {
// 1
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()
// 2
publisher1
.zip(publisher2)
.sink(receiveCompletion: { _ in print("Completed") },
receiveValue: { print("P1: \($0), P2: \($1)") })
.store(in: &subscriptions)
Now send values to publisher1 and publisher2 in any order you want, again sending completion events when you’re done.
// 3
publisher1.send(1)
publisher1.send(2)
publisher2.send("a")
publisher2.send("b")
publisher1.send(3)
publisher2.send("c")
publisher2.send("d")
// 4
publisher1.send(completion: .finished)
publisher2.send(completion: .finished)
}
Run the playground. You’ll see that the tuples are emitted in order - for example, the first tuple to be emitted is composed of the first values from each publisher (1,a) - even though other values have been sent to publisher1 before a is sent to publisher2. Each subsequent pairing - if they exists before completion events are sent - get emitted for downstream consumption, so (2,b) and (3,c) also get emitted here.
In this episode you learned about some more advanced combining operators. While switchToLatest is relatively complex, it’s extremely useful. It takes a publisher that emits publishers, switches to the latest publisher and cancels the subscription to the previous publisher.
merge(with:) lets you interleave values from multiple publishers. combineLatest emits the latest values of all combined publishers whenever any of them emit a value, once all of the combined publishers have emitted at least one value. zip pairs emissions from different publishers, emitting a tuple of pairs after all publishers have emitted a value.
You can mix combination operators to create interesting and complex relationships between publishers and their emissions.
I think it’s about time to combine all of your knowledge about combination operators and do another challenge! When you’re ready, head on over to the next episode to put your skills to the test.