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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started With RxSwift and RxCocoa
25 mins
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.
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:
Tap on a chocolate row to add that product to your cart:
Tap on the cart in the upper right-hand corner. On the next page, you can checkout or reset the cart:
Choosing Checkout results in a credit card entry 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:
- viewWillAppear(_:): Before showing the view controller.
- 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: [])
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:
- Grab the shopping cart’s
chocolates
variable as anObservable
. - Call
subscribe(onNext:)
on thatObservable
to discover changes to theObservable
’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 yourObservable
. You’ll keep getting these notifications until you either unsubscribe or dispose of your subscription. What you get back from this method is anObserver
conforming toDisposable
. - Add the
Observer
from the previous step to yourdisposeBag
. 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:
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:
Tap on some chocolates — the number of items in the cart now automatically updates!
Success! You can now add all the chocolates to the cart.