10 min read

The basic idea behind Reactive Programming (RP) is that of asynchronous data streams, such as the stream of events that are generated by mouse clicks, or a piece of data coming through a network connection. Anything can be a stream; there are really no constraints. The only property that makes it sensible to model any entity as a stream is its ability to change at unpredictable times. The other half of the picture is the idea of observers, which you can think of as agents that subscribe to receive notifications of new events in a stream. In between, you have ways of transforming those streams, combining them, creating new streams, filtering them, and so on.

You could look at RP as a generalization of Key-Value Observing (KVO), a mechanism that is present in the macOS and iOS SDKs since their inception. KVO enables objects to receive notifications about changes to other objects’ properties to which they have subscribed as observers. An observer object can register by providing a keypath, hence the name, into the observed object.

This article is taken from the book Hands-On Design Patterns with Swift by Florent Vilmart, Giordano Scalzo, and Sergio De Simone.  This book demonstrates how to apply design patterns and best practices in real-life situations, whether that’s for new or already existing Swift projects. You’ll begin with a quick refresher on Swift, the compiler, the standard library, and the foundation, followed by the Cocoa design patterns to follow up with the creational, structural, and behavioral patterns as defined by the GoF.  To follow along with the examples implemented in this article, you can download the code from the book’s GitHub repository.

In this article, we will give a brief introduction to one popular framework for RP in Swift, RxSwift, and its Cocoa counterpart, RxCocoa, to make Cocoa ready for use with RP. RxSwift is not the only RP framework for Swift. Another popular one is ReactiveCocoa, but we think that, once you have understood the basic concepts behind one, it won’t be hard to switch to the other.

Using RxSwift and RxCocoa in reactive programming

RxSwift aims to be fully compatible with Rx, Reactive Extensions for Microsoft .NET, a mature reactive programming framework that has been ported to many languages, including Java, Scala, JavasScript, and Clojure. Adopting RxSwift thus has the advantage that it will be quite natural for you to use the same approach and concepts in another language for which Rx is available, in case you need to.

If you want to play with RxSwift, the first step is creating an Xcode project and adding the SwiftRx dependency. If you use the Swift Package Manager, just make sure your Package.swift file contains the following information:

If you use CocoaPods, add the following dependencies to your podfile:

    pod 'RxSwift', '~> 4.0'
    pod 'RxCocoa', '~> 4.0'

Then, run this command:

pod install

Finally, if you use Carthage, add this to Cartfile:

github "ReactiveX/RxSwift" ~> 4.0

Then, run this command to finish:

carthage update

As you can see, we have also included RxCocoa as a dependency. RxCocoa is a framework that extends Cocoa to make it ready to be used with RxSwift. For example, RxCocoa will make many properties of your Cocoa objects observable without requiring you to add a single line of code. So if you have a UI object whose position changes depending on some user action, you can observe its center property and react to its evolution.

Observables and observers

Now that RxSwift is set up in our project, let’s start with a few basic concepts before diving into some code:

  • A stream in RxSwift is represented through Observable<ObservableType>, which is equivalent to Sequence, with the added capability of being able to receive new elements asynchronously.
  • An observable stream in Rx can emit three different events: next, error, and complete.
  • When an observer registers for a stream, the stream begins to emit next events, and it does so until an error or complete event is generated, in which case the stream stops emitting events.
  • You subscribe to a stream by calling ObservableType.subscribe, which is equivalent to Sequence.makeIterator. However, you do not use that iterator directly, as you would, to iterate a sequence; rather, you provide a callback that will receive new events.
  • When you are done with a stream, you should release it, along with all resources it allocated, by calling dispose. To make it easier not to forget releasing streams, RxSwift provides DisposeBag and takeUntil. Make sure that you use one of them in your production code.

All of this can be translated into the following code snippet:

let aDisposableBag = DisposeBag()
 let thisIsAnObservableStream = Observable.from([1, 2, 3, 4, 5, 6])
let subscription = thisIsAnObservableStream.subscribe(
onNext: { print("Next value: \($0)") },
onError: { print("Error: \($0)") },
onCompleted: { print("Completed") })

// add the subscription to the disposable bag
// when the bag is collected, the subscription is disposed
subscription.disposed(by: aDisposableBag)
// if you do not use a disposable bag, do not forget this!
// subscription.dispose()

Usually, your view controller is where you create your subscriptions, while, in our example thisIsAnObservableStream, observers and observables fit into your view model. In general, you should make all of your model properties observable, so your view controller can subscribe to those observables to update the UI when need be.

In addition to being observable, some properties of your view model could also be observers. For example, you could have a UITextField or UISearchBar in your app UI and a property of your view model could observe its text property. Based on that value, you could display some relevant information, for example, the result of a query.

When a property of your view model is at the same time an observable and an observer, RxSwift provides you with a different role for your entity—that of a Subject. There exist multiple categories of subjects, categorized based on their behavior, so you will see BehaviourSubject, PublishSubject, ReplaySubject, and Variable. They only differ in the way that they make past events available to their observers.

Before looking at how these new concepts may be used in your program, we need to introduce two further concepts: transformations and schedulers.

Transformations

Transformations allow you to create new observable streams by combining, filtering, or transforming the events emitted by other observable streams. The available transformations include the following:

  • map: This transforms each event in a stream into another value before any observer can observe that value. For example, you could map the text property of a UISearchBar into an URL to be used to query some remote service.
  • flatMap: This transforms each event into another Observable. For example, you could map the text property of a UISearchBar into the result of an asynchronous query.
  • scan: This is similar to the reduce Swift operator on sequences. It will accumulate each new event into a partial result based on all previously emitted events and emit that result.
  • filter: This enables filtering of emitted events based on a condition to be verified.
  • merge: This merges two streams of events by preserving their ordering.
  • zip: This combines two streams of events by creating a new stream whose events are tuples made by the successive events from the two original streams.

Schedulers

Schedulers allow you to control to which queue RxSwift operators are dispatched. By default, all RxSwift operations are executed on the same queue where the subscription was made, but by using schedulers with observeOn and subscribeOn, you can alter that behavior. For example, you could subscribe to a stream whose events are emitted from a background queue, possibly the results of some lengthy tasks, and observe those events from the main thread to be able to update the UI based on those tasks’ outcomes. Recalling our previous example, this is how we could use observeOn and subscribeOn as described:

let aDisposableBag = DisposeBag()
 let thisIsAnObservableStream = Observable.from([1, 2, 3, 4, 5, 6])
   .observeOn(MainScheduler.instance).map { n in
      print("This is performed on the main scheduler")
   }
let subscription = thisIsAnObservableStream
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.subscribe(onNext: { event in
print("Handle \(event) on main thread? \(Thread.isMainThread)")
}, onError: { print("Error: \($0). On main thread? \(Thread.isMainThread)")
}, onCompleted: { print("Completed. On main thread? \(Thread.isMainThread)") })

subscription.disposed(by: aDisposableBag)

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 a pretty standard code, with the major difference that it returns an observable wrapping a URLSession.dataTask call. The 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!

If you found this post useful, do check out the book, Hands-On Design Patterns with Swift. This book provides a complete overview of how to implement classic design patterns in Swift.  It will guide you to build Swift applications that are scalable, faster, and easier to maintain.

Read Next

Reactive Extensions: Ways to create RxJS Observables [Tutorial]

What’s new in Vapor 3, the popular Swift based web framework

Exclusivity enforcement is now complete in Swift 5