Asynchronous networking – an example

Now we can take a look at a slightly more compelling example, showing off the power of reactive programming. Let's get back to our previous example: a UISearchBar collects user input that a view controller observes, to update a table displaying the result of a remote query. This is a pretty standard UI design. Using RxCocoa, we can observe the text property of the search bar and map it into a URL. For example, if the user inputs a GitHub username, the URLRequest could retrieve a list of all their repositories. We then further transform the URLRequest into another observable using flatMap. The remoteStream function is defined in the following snippet, and simply returns an observable containing the result of the network query. Finally, we bind the stream returned by flatMap to our tableView, again using one of the methods provided by RxCocoa, to update its content based on the JSON data passed in record:

searchController.searchBar.rx.text.asObservable()
.map(makeURLRequest)
.flatMap(remoteStream)
.bind(to: tableView.rx.items(cellIdentifier: cellIdentifier)) { index, record, cell in
cell.textLabel?.text = "" // update here the table cells
}
.disposed(by: disposeBag)

This looks all pretty clear and linear. The only bit left out is the networking code. This is pretty standard code that we've already seen in this and other chapters of the book, with the major difference that it returns an observable wrapping a URLSession.dataTask call. This following code shows the standard way to create an observable stream by calling observer.onNext and passing the result of the asynchronous task:

func remoteStream<T: Codable>(_ request: URLRequest) -> Observable<T> {

return Observable<T>.create { observer in
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
let records: T = try JSONDecoder().decode(T.self, from: data ?? Data())
for record in records {
observer.onNext(record)
}
} catch let error {
observer.onError(error)
}
observer.onCompleted()
}
task.resume()

return Disposables.create {
task.cancel()
}
}
}

As a final bit, we could consider the following variant: we want to store the UISearchBar text property value in our model, instead of simply retrieving the information associated with it in our remote service. To do so, we add a username property in our view model and recognize that it should, at the same time, be an observer of the UISearchBar text property as well as an observable, since it will be observed by the view controller to retrieve the associated information whenever it changes. This is the relevant code for our view model:

import Foundation
import RxSwift
import RxCocoa

class ViewModel {

var username = Variable<String>("")
init() {
setup()
}
setup() {
...
}
}

The view controller will need to be modified as in the following code block, where you can see we bind the UISearchBar text property to our view model's username property; then, we observe the latter, as we did previously with the search bar:

searchController.searchBar.rx.observe(String.self, "text")
.bindTo(viewModel.username)
.disposed(by: disposeBag)

viewModel.username.asObservable()
.map(makeURLRequest)
.flatMap(remoteStream)
.bind(to: tableView.rx.items(cellIdentifier: cellIdentifier)) { index, record, cell in
cell.textLabel?.text = "" // update here the table cells
}
.disposed(by: disposeBag)

With this last example, our short introduction to RxSwift is complete. There is much more to be said, though. A whole book could be devoted to RxSwift/RxCocoa and how they can be used to write Swift apps!