Combine — switchToLatest()

switchToLatest() is one of the most powerful operator in Combine as it enables to switch entire publisher subscriptions on the fly while canceling the pending publisher subscription, thus switching to the latest one.

Only publishers that emit publishers can use the `switchToLatest()` operator.

AnyPublisher<AnyPublisher<[Post], Never>, Never>

Every time a publisher receives a new `Publisher` as output, the previous subscriptions from that stream will automatically be cancelled.

The operator is highly useful when it comes to chaining operations with a network request.

— Use case

When the user taps a view, we want to trigger a network request that sends us back a list of posts.

First let’s implement a `Webservice` class to model our request.

struct Post: Decodable {
let userId, id: Int
let title, body: String
}
final class Webservice {
func fetch(request: Int) -> AnyPublisher<[Post], Never> {
let url = URL(
string: “https://jsonplaceholder.typicode.com/posts"
)!
return URLSession.shared.dataTaskPublisher(for: url)
.delay(
for: .seconds(Int.random(in: 3…6)),
scheduler: DispatchQueue.main
)
.map { $0.data }
.decode(type: [Post].self, decoder: JSONDecoder())
.replaceError(with: [])
.handleEvents(receiveOutput: { _ in print(“This is
response from request n° \(request) “) })
.eraseToAnyPublisher()
}
}

For testing purpose, we use the `delay()` operator to model our requests with different response times and print the response with the request number to track our different network calls.

Now, we need to handle the user tap gesture through a custom implementation of the `Subscription` and `Publisher` protocols.

Check out my article Combine — Handling UIKit’s gestures to see the full implementation.

class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
var requestCount = 0
override func viewDidLoad() {
super.viewDidLoad()
view.gesture()
.handleEvents(receiveOutput: { _ in self.requestCount +=
1 })
.flatMap { _ in
Webservice().fetch(request: self.requestCount) }
.sink(receiveValue: { _ in })
.store(in: &cancellables)
}
}

When a tap gesture is detected, we call the `fetch()` method from our `Webservice` class to retrieve the posts from the user.

We use `flatMap()` to trigger the request and `sink()` to create our subscriber.

Let’s tap our view five times and check the console.

// This is response from request n° 1
// This is response from request n° 3
// This is response from request n° 4
// This is response from request n° 2
// This is response from request n° 5

Five network requests are performed which is quite unnecessary (and potentially expensive) since we really are interested in the latest subscription only.

We need to use `switchToLatest()` to cancel previous subscriptions.

view.gesture()
.handleEvents(receiveOutput: { _ in self.requestCount += 1 })
.map { _ in Webservice().fetch(request: self.requestCount) }
.switchToLatest() // A new `Publisher` output will cancel the
previous subscriptions.
.sink(receiveValue: { _ in })
.store(in: &cancellables)

Let’s tap five times again and check out the console.

// This is response from request n° 5

Since previous subscriptions have been cancelled, the network requests didn’t go through giving us a one and only print statement from our latest publisher.

Follow me on Twitter for my latest articles.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store