Getting Started With RxSwift and RxCocoa

Ellen Shapiro

RxCocoa LogoIt’s great when code does exactly what you tell it to (unlike my cat). Change something in the program, tell the code to update, and it does. Good code!

Most programming in the Object-Oriented era has been imperative like that: Your code tells your program what to do and has many ways to listen to changes—but you generally must actively tell the system when something changes.

That’s fine as far as it goes, but wouldn’t it be even better if you could set things up so that when something in your app changes, the code updates automatically? That’s the basic idea of reactive programming: your application can react to changes in the underlying data without you telling it to do so directly. This makes it easier to focus on the logic at hand rather than maintaining a particular state.

This can be achieved in vanilla Objective-C or Swift, mostly through Key-Value Observation and using overridden setters or the didSet method in Swift, but sometimes those methods can be cumbersome to set up correctly. To avoid this problem, there are several different frameworks in both Objective-C and Swift to facilitate the use of reactive programming.

Note: If you’d like more background, Rui Peres has written a great article outlining the differences between major frameworks. The comments section is also an interesting window into how passionate folks can get about which framework is better.

Today you’ll use one of these frameworks, RxSwift, and its companion RxCocoa, to take a chocolate-buying app from annoyingly imperative to awesomely reactive.

What are RxSwift and RxCocoa?

RxSwift and RxCocoa are part of the suite of ReactiveX (usually abbreviated to “Rx”) language tools that span multiple programming languages and platforms.

While ReactiveX started as part of the .NET/C# ecosystem, it has grown extremely popular with Rubyists, JavaScripters and particularly Java and Android developers.

RxSwift is a framework for interacting with the Swift programming language, while RxCocoa is a framework that helps make Cocoa APIs used in iOS and OS X easier to use with reactive techniques.

ReactiveX frameworks are designed to be helpful by providing a common vocabulary for certain tasks used repeatedly across different programming languages. This (theoretically) makes it easier to focus on the syntax of the language itself, rather than wasting time figuring out how to map a common task to each new language.

Observables and Observers

Two concepts to be aware of for this tutorial are the Observable and the Observer.

  • An Observable is something which emits notifications of change.
  • An Observer is something which subscribes to an Observable, in order to be notified when it has changed.

You can have multiple Observers listening to an Observable. This means that when the Observable changes, it will notify all its Observers.

The DisposeBag

RxSwift and RxCocoa also have an additional tool to help deal with ARC and memory management: the DisposeBag. This is a virtual “bag” of Observer objects which are disposed of when their parent object is deallocated.

When deinit() is called on the object which has a DisposeBag as a property, the bag is “emptied” and each disposable Observer is automatically unsubscribed from what it was observing. This allows ARC to take back memory as it normally would.

Without a DisposeBag, you’d get one of two results: either the Observer would create a retain cycle, hanging on to what it’s observing indefinitely, or it could get deallocated out from under your object, causing a crash.

So to be a good ARC citizen, remember to add any Observable objects to the DisposeBag when you set them up. That way, they’ll be cleaned up nicely for you.

Getting Started

Let’s get to the chocolate! The starter app for this tutorial, Chocotastic, is available here. Download the zip file and and open the project in Xcode.

Note: The project utilizes CocoaPods, so you’ll need to open the Chocotastic.xcworkspace file in Xcode.

Build and run the application. Eventually you’ll see the following screen, which lists several kinds of chocolate you can buy from Europe, along with respective prices:

Initial State

Tapping on a chocolate row will add that product to your cart:

added chocolate

Tap on the cart in the upper right-hand corner to be taken to a page where you can either check out or reset the cart.

checkout

If you choose Checkout, a credit card entry form will be presented:

Credit Card Form

Later in the tutorial, you’ll come back to set this up using purely reactive programming. Tap the Cart button to return to the cart summary, then tap the Reset button to return to the main screen with an empty cart.

The Starting Point: Non-reactive

Now that you’ve seen what the application does, it’s time to examine how it works. Open ChocolatesOfTheWorldViewController.swift, where you’ll see some pretty standard UITableViewDelegate and UITableViewDataSource extensions.

There’s also a method, updateCartButton(), which updates the cart button with the current number of chocolates in the cart. This is called from two different places: in viewWillAppear(_:), whenever the view controller is about to be shown, and in tableView(_:didSelectRowAt:), after a new chocolate has been added to the cart.

These are both imperative ways of changing the count: you must explicitly call the method to update the count.

Right now you have to keep track of where you’re changing the value, but you’re going to rewrite the code to use a reactive technique. That way, the button will update on its own no matter how or where the count is changed.

RxSwift: Making the Cart Count Reactive

All of the methods referring to items in the cart use a ShoppingCart.sharedCart singleton. Open up ShoppingCart.swift and you’ll see a pretty standard setup of a variable on the singleton instance:

var chocolates = [Chocolate]()

Right now, changes to the contents of chocolates can’t really be observed. You could add a didSet closure to its definition, but that would only get called when the entire array, rather than any of its elements, was updated.

Fortunately, RxSwift has a solution. Replace the line creating the chocolates variable with this:

let chocolates: Variable<[Chocolate]> = Variable([])
Note: This change will cause a bunch of errors to show up in the sidebar, but you’ll fix those in a moment.

This syntax can be a little hard to wrap your head around, so it helps to understand what’s going on under the hood.

Rather than setting chocolates to a Swift array of Chocolates objects, you’ve now defined it as a RxSwift Variable that has a type of a Swift array of Chocolate objects.

Variable is a class, so it uses reference semantics—meaning that chocolates refers to an instance of Variable.

Variable has a property called value. This is where your array of Chocolate objects is stored.

The magic of Variable comes from a method called asObservable(). Instead of manually checking value every time, you can add an Observer to keep an eye on the value for you. When the value changes, the Observer lets you know so you can react to any updates.

The downside of this setup is that if you need to access or change something in that array of Chocolates, you must do it via the value property rather than directly; that’s why the compiler is throwing a tantrum and a fistful of errors. Time to fix them up!

In ShoppingCart.swift, look for the method totalCost() and change this line:

return chocolates.reduce(0) {

to:

return chocolates.value.reduce(0) {

In itemCountString(), change:

guard chocolates.count > 0 else {

to:

guard chocolates.value.count > 0 else {

and change:

let setOfChocolates = Set<Chocolate>(chocolates)

to:

let setOfChocolates = Set<Chocolate>(chocolates.value)

Finally, change:

let count: Int = chocolates.reduce(0) {

to:

let count: Int = chocolates.value.reduce(0) {

In CartViewController.swift, find reset() and change:

ShoppingCart.sharedCart.chocolates = []

to:

ShoppingCart.sharedCart.chocolates.value = []

Back in ChocolatesOfTheWorldViewController.swift, change the implementation of updateCartButton() to this:

cartButton.title = "\(ShoppingCart.sharedCart.chocolates.value.count) \u{1f36b}"

and in tableView(_:didSelectRowAt:), change this line:

ShoppingCart.sharedCart.chocolates.append(chocolate)

to the following:

ShoppingCart.sharedCart.chocolates.value.append(chocolate)

Whew! After all that, Xcode will be happy and there should be no errors. Now you can take advantage of the fact that chocolates can now be observed!

Go to ChocolatesOfTheWorldViewController.swift and add the following to the list of properties:

let disposeBag = DisposeBag()

This creates a DisposeBag you’ll use to ensure that the Observers you set up will be cleaned up when deinit() is called.

Add the following under the //MARK: Rx Setup comment:

//MARK: Rx Setup
 
private func setupCartObserver() {
  //1
  ShoppingCart.sharedCart.chocolates.asObservable()
    .subscribe(onNext: { //2
      chocolates in
      self.cartButton.title = "\(chocolates.count) \u{1f36b}"
    })
    .addDisposableTo(disposeBag) //3
}

This sets up a reactive Observer to update the cart automatically. As you can see, RxSwift makes heavy use of chained functions, meaning that each function takes the result of the previous function.

How that’s happening in this case:

  1. First, you grab the shopping cart’s chocolates variable as an Observable.
  2. You call subscribe(onNext:) on that Observable in order to find out about changes to the Observable’s value. subscribe(onNext:) accepts a closure that will be executed every time the value changes. The incoming parameter to the closure is the new value of your Observable, and you’ll keep getting these notifications until you either unsubscribe or your subscription is disposed. What you get back from this method is an Observer conforming to Disposable.
  3. You add the Observer from the previous step to your disposeBag to ensure that your subscription is disposed of when the subscribing object is deallocated.

To finish up, delete the imperative updateCartButton() method. This will cause errors to appear where it was being called in viewWillAppear(_:) and tableView(_:didSelectRowAt:).

To fix them, delete the entire viewWillAppear(_:) method (since calling updateCartButton() is the only thing it’s doing beyond calling super), then delete the call to updateCartButton() in tableView(_:didSelectRowAt:).

Build and run. You’ll see the list of chocolates:

RXCocoa updated list of chocolates

But notice that the button for the cart just says “Item”. And that when you start tapping on the list of chocolates, nothing happens. What went wrong?

You created a function to set up your Rx Observers, but right now there’s nothing actually calling that function, so the Observers aren’t being set up. To fix this, add the following to viewDidLoad():

setupCartObserver()

Build and run the application to see the list of chocolates again:

initial state

Tap on some chocolates—the number of items in the cart now automatically updates!

added chocolate

Success! All the chocolates can now be added to the cart.

Rage Chocolate

RxCocoa: Making the TableView Reactive

Now that you’ve made the cart reactive using RxSwift, you’ll use RxCocoa to make your UITableView reactive too.

RxCocoa has reactive APIs for several different types of UI element. These give you options to set up things like UITableViews without the need to override delegate or data source methods directly.

To demonstrate how this works, delete the entire UITableViewDataSource and UITableViewDelegate extensions and all of their methods. Next, delete the assignments to tableView.dataSource and tableView.delegate from viewDidLoad().

Build and run the application, and you’ll see that your happy little table view full of chocolates has suddenly become quite sad and empty:

no chocolate for you

That’s no fun. Time to restore the chocolates!

First, in order to have a reactive table view, you need something for the table view to react to. Still in ChocolatesOfTheWorldViewController.swift, update the europeanChocolates property to be an Observable:

let europeanChocolates = Observable.just(Chocolate.ofEurope)

The just(_:) method indicates that there won’t actually be any changes to the underlying value of the Observable, but that you still want to access it as an Observable value.

Note: Sometimes, calling just(_:) is an indication that using Reactive programming might be overkill—after all, if a value never changes, why use a programming technique designed to react to changes? In this example, you’re using it to set up reactions of table view cells which will change, but it’s always a good idea to look carefully at how you’re using Rx. Just because you have a hammer doesn’t mean every problem is a nail. :]

Now that you’ve made europeanChocolates an Observable, add the following:

private func setupCellConfiguration() {
  //1
  europeanChocolates
    .bindTo(tableView
      .rx //2
      .items(cellIdentifier: ChocolateCell.Identifier,
             cellType: ChocolateCell.self)) { // 3
        row, chocolate, cell in
        cell.configureWithChocolate(chocolate: chocolate) //4
      }
      .addDisposableTo(disposeBag) //5
}

What’s going on here:

  1. You call bindTo(_:) to associate the europeanChocolates observable with the code that should get executed for each row in the table view.
  2. By calling rx, you are able to access the RxCocoa extensions for whatever class you call it on – in this case, a UITableView.
  3. You call the Rx method items(cellIdentifier:cellType:), passing in the cell identifier and the class of the cell type you want to use. This allows the Rx framework to call the dequeuing methods that would normally be called if your table view still had its original delegates.
  4. You pass in a block to be executed for each new item. You’ll get back information about the row, the chocolate at that row, and the cell, making it super-easy to configure the cell.
  5. You take the Disposable returned by bindTo(_:) and add it to the disposeBag.

The values normally generated by tableView(_:numberOfRowsInSection:) and numberOfSections(in:) are now automatically calculated based on the data being observed. tableView(_:cellForRowAt:) is effectively replaced by the closure.

Go to viewDidLoad() and add a line calling your new setup method:

setupCellConfiguration()

Build and run the application, and voilà! Your chocolates have returned.

initial state

When you try to tap on each chocolate, however, they aren’t being added to the cart. Did you break something with your earlier Rx method?

Nope! By removing tableView(_:didSelectRowAt:), you’ve taken away anything which would recognize cell taps or know how to handle them.

To remedy this, there’s another extension method RxCocoa adds to UITableView called modelSelected(_:), which returns an Observable you can use to watch information about when model objects are selected.

Add the following method:

private func setupCellTapHandling() {
  tableView
    .rx
    .modelSelected(Chocolate.self) //1
    .subscribe(onNext: { //2
      chocolate in
      ShoppingCart.sharedCart.chocolates.value.append(chocolate) //3
 
      if let selectedRowIndexPath = self.tableView.indexPathForSelectedRow {
        self.tableView.deselectRow(at: selectedRowIndexPath, animated: true)
      } //4
    })
    .addDisposableTo(disposeBag) //5
}

Going through this step by step:

  1. You call the table view’s reactive extension’s modelSelected(_:) function, passing in the Chocolate model to get the proper type of item back. This returns an Observable.
  2. Taking that Observable, you call subscribe(onNext:), passing in a trailing closure of what should be done any time a model is selected (i.e., a cell is tapped).
  3. Within the trailing closure passed to subscribe(onNext:), you add the selected chocolate to the cart.
  4. Also in the closure, you make sure that the tapped row is deselected.
  5. subscribe(onNext:) returns a Disposable. You add that Disposable to the disposeBag.

Finally, go to viewDidLoad() and add a line calling your new setup method:

setupCellTapHandling()

Build and run. You’ll see your familiar list of chocolates:

initial state

But now you can add chocolates to your heart’s (or stomach’s) content!

added chocolate

RxSwift and Direct Text Input

Another useful feature of RxSwift is its ability to take and react to direct text input by the user.

To get a taste of handling text input reactively, you’ll add some simple validation and card type detection to the credit card entry form.

Credit card entry in non-reactive programs is handled by a tangle of UITextFieldDelegate methods, often with each one containing a mess of if/else statements indicating what actions and logic should be applied based on which text field is currently being edited.

Reactive programming ties the handling more directly to each input field, as well as clarifying what logic applies to which text field.

Go to BillingInfoViewController.swift. At the top of the class, add the following:

private let disposeBag = DisposeBag()

As before, this defines a DisposeBag to ensure all your Observables are properly disposed of when instances of your class are deallocated.

One thing that’s helpful to users putting in a credit card number is to display what type of credit card they’re inputting based on known card types.

To do this, add the following below the //MARK: - Rx Setup comment:

//MARK: - Rx Setup
 
private func setupCardImageDisplay() {
  cardType
    .asObservable()
    .subscribe(onNext: {
      cardType in
      self.creditCardImageView.image = cardType.image
    })
    .addDisposableTo(disposeBag)
}

In a moment, you’ll use this to update the card image based on changes to the card type. It adds an Observer to the value of a variable with a closure to execute when it changes, then makes sure that the Observer is properly disposed of in the disposeBag.

Now for the fun part: Text change handling.

Since a user might type quickly, you may not want to run your validation for every single key press. This can get very computationally expensive and lead to a laggy UI.

A good way around this is to debounce or throttle how quickly you’re putting the user’s input through a validation process. This means the input will only be validated at the throttle interval, rather than every single time it changes, so fast typing won’t grind your whole app to a halt.

Throttling is a particular specialty of RxSwift, since there’s often a fair amount of logic to be run when something changes. In this case, there’s not a crazy amount of logic, but there’s enough to make a small throttle worthwhile.

First, add the following just below the other property declarations in BillingInfoViewController:

private let throttleInterval = 0.1

This defines a constant for the throttle length in seconds.

Now add the following:

private func setupTextChangeHandling() {
  let creditCardValid = creditCardNumberTextField
    .rx
    .text //1
    .throttle(throttleInterval, scheduler: MainScheduler.instance) //2
    .map { self.validate(cardText: $0) } //3
 
  creditCardValid
    .subscribe(onNext: { self.creditCardNumberTextField.valid = $0 }) //4
    .addDisposableTo(disposeBag) //5
}
Note: If you get a “Generic parameter R could not be inferred” compiler error when setting up creditCardValid, you can generally solve it by explicitly stating its type to silence the compiler, i.e. let creditCardValid: Observable. In theory, the compiler should be able to infer this, but sometimes it needs a little help. :]

What this code does:

  1. text is another RxCocoa extension (as indicated by having to call rx before being able to use it), this time to UITextField. It returns the contents of the text field as an Observable value.
  2. You throttle the input so that the validation you’re setting up is only run based on the interval defined above. The scheduler parameter is a more advanced concept, but the short version is that it’s tied to a thread. Since you want to keep everything on the main thread, use MainScheduler.
  3. You transform the throttled input by applying it to validate(cardText:), already provided for you by the class. If the card input is valid, the ultimate value of the boolean being observed will be true.
  4. You take the Observable value you’ve created and subscribe to it, updating the validity of the text field based on the incoming value.
  5. You add the resulting Disposable to the disposeBag.

Add the following code to the bottom of setupTextChangeHandling() to create Observable variables for the expiration date and card security code (a.k.a. the CVV):

let expirationValid = expirationDateTextField
  .rx
  .text
  .throttle(throttleInterval, scheduler: MainScheduler.instance)
  .map { self.validate(expirationDateText: $0) }
 
expirationValid
  .subscribe(onNext: { self.expirationDateTextField.valid = $0 })
  .addDisposableTo(disposeBag)
 
let cvvValid = cvvTextField
  .rx
  .text
  .map { self.validate(cvvText: $0) }
 
cvvValid
  .subscribe(onNext: { self.cvvTextField.valid = $0 })
  .addDisposableTo(disposeBag)

Now that you’ve got Observable values set up for the validity of the three text fields, add the following:

let everythingValid = Observable
  .combineLatest(creditCardValid, expirationValid, cvvValid) {
    $0 && $1 && $2 //All must be true
  }
 
everythingValid
  .bindTo(purchaseButton.rx.enabled)
  .addDisposableTo(disposeBag)

This uses Observable’s combineLatest(_:) method to take the three Observables you’ve already made and generate a fourth, called everythingValid, which is either true or false depending on whether all three inputs are valid.

everythingValid is then bound to the enabled property on UIButton’s reactive extension, so that the purchase button’s state is controlled by everythingValid’s value.

If all three fields are valid, the underlying value of everythingValid will be true. If not, the underlying value will be false. In either case, rx.enabled will cause the underlying value to be applied to the purchase button, which is only enabled when the credit card details are valid.

Now that you’ve created your setup methods, add the code to call them to viewDidLoad():

setupCardImageDisplay()
setupTextChangeHandling()

Build and run the application. To get to the credit card input, tap a chocolate to add it to the cart, then tap the cart button to go to the cart. As long as you have at least one chocolate in your cart, the checkout button should be enabled:

checkout

Tap the Checkout button, which will take you to the credit card input screen:

Credit Card Form

Type 4 into the Card Number text field—you’ll see the card image instantly change to Visa:

Visa

Delete the 4, and the card image will change back to unknown. Type in 55, and the image will change to MasterCard.

Simulator Screen Shot Jul 4, 2016, 7.47.36 PM

Neat! This app covers the four major types of credit cards in the United States (Visa, MasterCard, American Express and Discover). If you have one of those types of credit cards, you can input the number to see the correct image pop up and check to see if the number is valid.

Note: If you don’t have one of those credit cards, you can use one of the test card numbers that PayPal uses to test their card sandbox—these should pass all local validation in the application, even though the numbers themselves are not actually usable.

Once a valid credit card number is input with an expiration date (using a two-digit month and a four-digit year) anytime in the future and an appropriate-length CVV, the Buy Chocolate! button will enable:

enabled_checkout

Tap the button to see a summary of what you bought and how you paid for it, as well as a little easter egg:

success

Congratulations! Thanks to RxSwift and RxCocoa, you can buy as much chocolate as your dentist will let you get away with. :]

Where to Go From Here?

The finished project can be found here.

If you want a challenge, try adding a couple things to make this application even more reactive:

  • Change the CartViewController to use a reactive table view (instead of a label) to display the contents of the cart.
  • Allow the user to add or remove chocolates directly from the cart, automatically updating the price.

Now that you’ve gotten a taste of Rx programming, here are a few more resources to help you continue on your journey:

Finally, our own Marin Todorov has a great blog about his adventures in Reactive programming called rx_marin. Check it out!

Got questions or other Rx resources to suggest? Sound off below in the comments, or in the forums.

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Ellen Shapiro

Ellen Shapiro is the Lead Mobile Developer for SpotHero in Chicago, Illinois. She is working in her spare time to help bring Hum to life. She’s also developed several independent applications through her personal company, Designated Nerd Software.

When she's not writing code, she's usually tweeting about it.

Other Items of Interest

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 19 total!

Swift Team

... 16 total!

iOS Team

... 30 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Apple Game Frameworks Team

... 10 total!

Unity Team

... 11 total!

Articles Team

... 11 total!

Resident Authors Team

... 11 total!