combineLatest beyond the basics

In the last couple of weeks I got to talk to few people who were either still struggling to understand what exactly does combineLatest do or didn’t know about a more recent power feature.

So here it is … a post about combineLatest!

What does the combineLatest operator do?

combineLatest is super useful and it was the first thing in RxSwift that really made see how powerful Rx is and showed me what kind of problems can be easily solved with RxSwift.

So what does it do? Any time any of the source sequences emits an element, also combineLatest’s result seqiemce emits an element, which contains all the latest values of each of the source sequences.

Let’s look at a marble diagram (source: http://rxmarbles.com/#combineLatest):

combineLatest schema

We have two sequences S1 and S2, which emit values - the former emits numbers and the latter - letters.

After all source sequences emit for the first time (e.g. they do have a latest value), when any of the sources emits an element combineLatest grabs the latest values of all sources and emits the combined result.

So let’s have a look what happens on the diagam above:

  • first, S1 emits 1 - since not all sources have emitted at that time, the result does not emit
  • then, S1 emits 2 - same thing happens since S2 still hasn’t emitted anything (e.g. has no latest value)
  • then S2 emits A - at that point all sources have a latest element so combineLatest takes A and combines it with the latest element from S1 2. (“What about 1?” some of you are asking. Well - tough luck, 1 didn’t make it.)
  • later S2 emits another element B, again combineLatest combines it with the latest element from S1, which is 2
  • and so forth…

combineLatest is particularly useful handling UI input (you can react to all changes in any of the UI’s text fields, switches, etc) or when you fire a number of networking requests and need to react when any of the http responses completes.

Using combineLatest with a dynamic number of source observables

I created a small example for this article, and added the code to my https://github.com/icanzilb/RxSwiftoniOS repository. If you clone the repo and run it - it’s under number 4 in the main menu.

If you’re only interested in the code just peak inside here: https://github.com/icanzilb/RxSwiftoniOS/blob/master/RxSwiftiOS/CombineViewController.swift.

One of the lesser known features of combineLatest is that you can either use it with a given number of sources (which can be two, three, four, or more but still a fixed number), like so:

Observable.combineLatest(S1, S2, S3) { value1, value2, value3 in
	print("latest values: ...")
}

But if you don’t know how many sources you have in advance or in case you want to be able to easily map over the emitted values you can also provide a collection of source observables to combineLatest. Like so:

Observable.combineLatest(sources) { values in
  print(values.map { "value: $0" })
}

So in the example I wrote for this article I’m fetching JSON with the list of my followers from GitHub and show them in a collection view. For each follower object in the JSON response I fetch their avatar image and show it in a collection cell.

Let’s start with fetching the followers JSON:

Observable.just("https://api.github.com/users/icanzilb/followers")
  .map { url in
    let apiUrl = URLComponents(string: url)!
    return URLRequest(url: apiUrl.url!)
  }
  .flatMapLatest { request in
    return URLSession.shared.rx.json(request: request)
      .catchErrorJustReturn([])
  }
  .map { json -> [User] in
    guard let users = json as? [JSONObject]  else { return [] }
    return users.flatMap(User.init)
  }

This code makes a request to https://api.github.com/users/icanzilb/followers and gets back a list of JSON objects, then it maps them to an array of User objects. Each User has a login and an avatarUrl properties.

I bind the users list to the followers Variable and will map each user object to an HTTP request, which will fetch their avatar image from GitHub:

followers.asObservable()
  .map { users -> [Observable<Data>] in
    return users.map { user in
      let request = URLRequest(url: URL(string: user.avatarUrl)!)
      return URLSession.shared.rx.data(request: request)
        .catchErrorJustReturn(UIImage.blank)
    }
  }

The map above takes in the list of user objects [User] and maps those to URLSession observable responses [Observable<Data>].

Neat! I’m passing the array of observables directly to combineLatest and convert the returned collection of Data values into images:

.flatMap(Observable.combineLatest)
.map { $0.map(UIImage.fromData) }

I had to add a quick extension to UIImage to add a static method to make images out of data values:

extension UIImage {
  static func fromData(data: Data) -> UIImage { return UIImage(data: data)! }
}

Nice. Now when all my HTTP requests get their response combineLatest gives me a list of data values, which I turn into images and can bind to a variable or otherwise make use of.

If you run the linked above demo project you will see that the code fetches all followers from GitHub, then fires off a bunch of requests, gets all their avatars and once that’s done it does a single data reload on the collection view and shows them all at once:

Ok, that’s all about using combineLatest with a collection of source sequences. But wait - there’s more to be said on the topic …

All at once vs. as they come

When using combineLatest (especially with network requests) you might want to have two very different use cases.

In the example above you saw how to fire simulatenously an arbitrary number of network requests and only once all of them have completed emit the result of all of them.

But how about if you didn’t want ot wait for all requests to complete? What if you preferred to update the UI as each of the requests receives a response?

Well, remember the discussion above? combineLatest does emit for each element from the source sequences but only after all of them have a latest value.

So, I wanted to show that as well in the demo project so you will actually find two different methods in the view controller source code getAllAtOnce() and getAsTheyCome(). You can switch between those in the app UI by toggling the segment control at the top of the screen.

So the only difference in the code of the two methds (and I mean the only) is that when I’m reloading the collection whenever each of the avatar requests completes I’m providing a default value for each observable.

So here’s the code to fetch all at once:

.map { users -> [Observable<Data>] in
  return users.map { user in
    let request = URLRequest(url: URL(string: user.avatarUrl)!)
    return URLSession.shared.rx.data(request: request)
      .catchErrorJustReturn(UIImage.blank)
  }
}

And here’s the code that makes combineLatest’s result emit as each response comes in:

.map { users -> [Observable<Data>] in
  return users.map { user in
    let request = URLRequest(url: URL(string: user.avatarUrl)!)
    return URLSession.shared.rx.data(request: request)
      .startWith(UIImage.blank)
      .catchErrorJustReturn(UIImage.blank)
  }
}

Yes, I’m sure you spotted the difference - the source has a default value (in my case it’s an empty avatar image). This way the collection starts out with a default image for each follower and then as the images are fetched one by one the collection reloads to display each new one. (Yes, not optimal but the point here is to discuss combineLatest.)

So the screen starts with all sources’ latest values (at first those are the default images):

And then you can see how the collection keeps reloading to display each of the received images:

To try this in the app tap on the As they come segment on top.

And that’s a wrap!

Where to go from here?

Once more, the demo project for this article you will find on GitHub here: https://github.com/icanzilb/RxSwiftoniOS.

I hope this post has been helpful and has shown you a couple of nice combineLatest features. Of course these few examples don’t cover everything - if you’d like to go deeper into combining operators you’re welcome to check Chapter 9, “Combining Operators” and Chapter 10, “Combining Operators in Practice”, which cover far more ground than this post.

The book is available at http://raywenderlich.com/store - this is where you can see any updates, discuss in the website forums, etc.

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.