Getting Started With RxSwift and RxCocoa

Use the RxSwift framework and its companion RxCocoa to take a chocolate-buying app from annoyingly imperative to awesomely reactive. By Ron Kliffer.

4.6 (87) · 2 Reviews

Download materials
Save for later
Share
Update note: Ron Kliffer updated this tutorial for Xcode 10.2, Swift 5, iOS 12 and RxSwift 5.0. Ellen Shapiro wrote the original.

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

Most programming in the object-oriented era has been imperative. Code tells your program what to do and has many ways to listen to changes. However, you must tell the system when something changes.

Wouldn’t it be better if you could set things up so the code updates reflect changes automatically? That’s the idea of reactive programming: Your application can react to changes in the underlying data without you telling it to do so. This makes it easier to focus on the logic at hand rather than maintaining a particular state.

You can achieve this in Swift through Key-Value Observation and using didSet, but it can be cumbersome to set up. Alternatively, there are several frameworks in Swift that facilitate reactive programming.

Note: For more background, see Colin Eberhardt’s great article outlining the differences between major frameworks. Check out the comments section for further discussion.

In this tutorial, you’ll use the RxSwift framework and its companion RxCocoa to take a chocolate-buying app from imperative to reactive.

What are RxSwift and RxCocoa?

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

While ReactiveX started as part of the .NET/C# ecosystem, it’s 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 makes Cocoa APIs used in iOS and OS X easier to use with reactive techniques.

ReactiveX frameworks provide a common vocabulary for tasks used repeatedly across different programming languages. This makes it easy to focus on the syntax of the language itself rather than figuring out how to map a common task to each new language.

Observables and Observers

Two key concepts are the Observable and the Observer.

  • An Observable emits notifications of change.
  • An Observer subscribes to an Observable and gets notified when that Observable has changed.

You can have multiple Observers listening to an Observable. When the Observable changes, it will notify all its Observers.

The DisposeBag

The DisposeBag is an additional tool RxSwift provides to help deal with ARC and memory management. Deallocating a parent object results in the disposal of Observer objects in the DisposeBag.

When deinit() is called on the object that holds the DisposeBag, 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 be deallocated, causing a crash.

To be a good ARC citizen, remember to add any Observable objects to the DisposeBag when you set them up. The DisposeBag will clean up nicely for you.

Getting Started

It’s time for you to get to the chocolate! Use the Download Materials button at the top or bottom of this tutorial to download the starter project. After you’ve done so, open Chocotastic.xcworkspace.

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

Initial State

Tap on a chocolate row to add that product to your cart:

added chocolate

Tap on the cart in the upper right-hand corner. On the next page, you can checkout or reset the cart:

checkout

Choosing Checkout results in a credit card entry form:

Credit Card Form

Later in the tutorial, you’ll come back to set this up using purely reactive programming. For now, 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: Nonreactivity

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

Look at updateCartButton(). This method updates the cart button with the current number of chocolates in the cart. The method updates the cart in two instances:

  1. viewWillAppear(_:): Before showing the view controller.
  2. tableView(_:didSelectRowAt:): After a user adds a new chocolate to the cart.

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

You’re going to rewrite the code to use a reactive technique. That way, the button will update on its own.

RxSwift: Making the Cart Count Reactive

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

var chocolates: [Chocolate] = []

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

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

let chocolates: BehaviorRelay<[Chocolate]> = BehaviorRelay(value: [])
Note: This change will cause errors in the sidebar. You’ll fix those in a moment.

This syntax can be a little hard to wrap your head around. It helps to understand what’s going on. Essentially, rather than setting chocolates to a Swift array of Chocolate objects, you’ve now defined it as a RxSwift BehaviorRelay that has a type of a Swift array of Chocolate objects.

BehaviorRelay is a class, so it uses reference semantics. This means that chocolates refers to an instance of BehaviorRelay.

BehaviorRelay has a property called value. This stores your array of Chocolate objects.

The magic of BehaviorRelay 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 is that if you need to access or change something in that array of chocolates, you must do it via accept(_:). This method on BehaviorRelay updates its value property. That’s why the compiler is throwing a tantrum and presenting a fistful of errors. Time to fix them!

In ShoppingCart.swift, find 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.accept([])

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:

let newValue =  ShoppingCart.sharedCart.chocolates.value + [chocolate]
ShoppingCart.sharedCart.chocolates.accept(newValue)

Whew! That should make Xcode happy and take care of the errors. Now you can take advantage of reactive programming and observe chocolates!

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

private let disposeBag = DisposeBag()

This creates the DisposeBag you’ll use to clean up any Observers you set up.

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

func setupCartObserver() {
  //1
  ShoppingCart.sharedCart.chocolates.asObservable()
    .subscribe(onNext: { //2
      [unowned self] chocolates in
      self.cartButton.title = "\(chocolates.count) \u{1f36b}"
    })
    .disposed(by: 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. Grab the shopping cart’s chocolates variable as an Observable.
  2. Call subscribe(onNext:) on that Observable to discover changes to the Observable’s value. subscribe(onNext:) accepts a closure that executes every time the value changes. The incoming parameter to the closure is the new value of your Observable. You’ll keep getting these notifications until you either unsubscribe or dispose of your subscription. What you get back from this method is an Observer conforming to Disposable.
  3. Add the Observer from the previous step to your disposeBag. This disposes of your subscription upon deallocating the subscribing object.

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

To fix them, delete the entire viewWillAppear(_:) 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

Uh-oh. The cart button only says Item, and 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 there’s nothing calling that function, so the Observers aren’t responding. To fix this, open ChocolatesOfTheWorldViewController.swift and add the following at the end of 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! You can now add all the chocolates to the cart.