Getting Started with RxSwift and RxCocoa
Today I’d like to talk to you about a tool that I enjoy using the most and that I believe made me a better developer in my 5 years of iOS developer life. I’d like to point out that we used RxSwift intensely in my last two jobs. Therefore, I wanted to prepare an article for people who want to start in reactive programming.
The original article is written in Turkish. You can check it out.
What are RxSwift and RxCocoa?
RxSwift library allows us to use Swift disparately. With this library, asynchronous programming becomes easier to do and more legible. It allows you to build more solid architectures and applications with higher quality.
RxCocoa library allows us to use Cocoa APIs used in iOS and OS X with reactive technics.
Observables and Observers
One of the concepts you’ve got to know in this article is Observable and the other is Observer.
- Observable means the constructs that bring out the changes.
- Observer constructs are the ones that subscribe to Observable constructs and are informed when there’s a change.
DisposeBag
RxSwift and RxCocoa contain a tool named DisposeBag which helps ARC and memory management. We can think DisposeBag as a virtual bag that carries Observer objects. We can use DisposeBag tool to properly dispose of Observers when the parent objects, which we define Observers with, are deallocated.
You may feel a little confused about the terms but you’ll understand them better later on in the article.
Let’s Start
I prepared a little application whose reactive technics aren’t being used to be able to explain RxSwift and RxCocoa libraries to you better. We will turn the application into a reactive one step by step later on in the article. CoffeeShop application that I prepared contains user homepage, menu page where you can see all the coffees in the shop, detail page where you can order coffees and cart page where you can see the coffees you want to buy. I specifically suggest that you download the initial state of the project and do all the changes step by step with me.
You can clone the starter and final version of the project using git clone https://github.com/Goktug/RxSwift-RxCocoa-CoffeeShop-Medium.git command in the terminal.
First, you need to download the project and extract it from the zip file. Then, you need to run pod install command to install RxSwift and RxCocoa Pods. After the Pod has been installed, you need to open CoffeeShop.xcworkspace file with Xcode.
Homepage
There’s a homepage where we ask the user for their e-mail and password information. We are not going to use a real authentication mechanism that communicates with the backend server here. We are only going to look at the requirement of the e-mail being in the right format and the password being at least 6 characters. If the e-mail and the password meet with the requirements we want, we are going to direct the user to the menu page.
When we open LoginViewController.swift file, we see the E-mail, Password texfield and Login button that are connected with IBOutlet. In addition to this, logInButtonPressed() function calls when they press the login button. As you can see, there’s no validation mechanism for the text fields at the moment. Let’s add a validation mechanism to this page using reactive programming principles.
First, we need to import RxSwift and RxCocoa libraries to the top of the file.
import RxSwift
import RxCocoaclass LoginViewController: UIViewController { ... }
We can receive the text input entries of the text field instantly in a reactive way thanks to RxSwift and RxCocoa. Thus, we will control the validness of the e-mail and password area every time the user enters input. If both of the inputs meet our requirements, we will activate the Log In button. In non-reactive programming, we needed to implement the methods of the UITextFieldDelegate delegate to our class and set a lot of complicated if/else conditions. Thanks to reactive programming, we can directly reach these inputs.
Let’s add the code below to the LoginViewController
class.
private let disposeBag = DisposeBag()
Like I mentioned before when the instance of our class is deallocated, DisposeBag
allows us to properly dispose of its Observers.
Let’s add the e-mail validation function below inside our class. We are going to use that to verify the e-mail input we get from the user.
Now, it’s time for the e-mail validation part. Let’s add the code below to our viewDidLoad method.
Let’s see step by step what these codes do:
- Thanks to
text
, one of the extensions of RxCocoa, we can reach the text parameter of UITextField as an Observable variable. - Normally, the text data of textfield is return as
String
ornil
.orEmpty
makes sure the data always comes inString
type. - The validness of the input is checked with the
validateEmail
method. It turns intotrue
if the e-mail is in the right format,false
if not. Because we want theemailValid
variable to become a Bool type Observable variable, we need to turn the input into Bool type withmap
through String. - The
debug
function allows us to see the course of events on the console. You don’t have to add this, it’s optional. I recommend you to add to be able to observe the process better at the beginning. - If there are other Observers subscribing to this Observable variable, it’s ensured thanks to
share
this process isn’t repeated.
Now, this code runs each time the user typed something. We can optimize it by getting the inputs not each time the user typed something. The throttle
or debounce
filtering operators that RxSwift offers are ideally suited for this job. We are going to use the throttle operator since it’s more suitable for us.
.throttle(0.1, scheduler: MainScheduler.instance)
Let’s take this code as an example. Thanks to this code, it waits for another input to come for 0.1 second after each time an input comes. If an input comes within this period, it waits another 0.1 second for another input to come. If it doesn’t get another input in 0.1 second after the last input has been taken, it gives the final state of the input to flow. And this prevents our application to run unnecessary codes in fast input entries and the lockouts that might arise because of that.
Let’s integrate the throttle mechanism to our code. Let’s add the code blow to the top of the class.
private let throttleInterval = 0.1
And let’s change the code below with the one we wrote before.
You need to do the same things to control the password input. Let’s add the code below under the code we wrote above.
Let’s take a look at the different code since it’s exactly the same with the codes we wrote for e-mail except for the map code:
- We write this requirement into our map function since we only have the requirement of at least 6 character in the password input. We give the String type input to the flow by turning it into Bool type.
Now, we need to create an Observable variable that allows us to check the validness of our inputs at the same time.
CombineLatest is one of the concatenation operators of RxSwift. We can gather multiple same-typed Observable variables under one Observable variable thanks to this method.
We want our everythingValid
variable to be true when we combine the emailValid
and passwordValid
variables with the && operator and If both are true
, It will return true. Otherwise, it will return false
. We will connect our Observable variable to the isEnabled
parameter of the button named logInButton
. And that will allow the login button to be active when the e-mail and password inputs are true and when clicked, for them to go to menu page.
Let’s add the code below to the bottom of the viewDidLoad
method.
Let’s take a look at what these codes do step by step.
bind(to:)
function allows us to connect the Observable variable with the same type. (It turns the isEnabled variable of therx.isEnabled
button, one of the extensions of RxCocoa, to an Observable variable.)bind(to:)
function returns a Disposable typed variable. We need to add this todisposeBag
of this class to dispose of the Observer we created when theLoginViewController
class deallocated.
We have completed the functionality of our homepage now. We can move on to our next page.
Menu Page
We can view the coffee types in the shop in this page. We can go to the detail page of the coffee we want to buy by clicking on it. We can also go to the Cart page by clicking on the cup icon on the top right side of the page. We can see the coffee number that we added in the little red circle on top of the icon.
Now firstly, we’ll make our UITableView
UI component reactive using RxCocoa. RxCocoa has a number of reactive APIs for UITableView
. This way, we won’t have to override the UITableViewDataSource
and UITableViewDelegate
delegates. RxCocoa will do that for us.
Firstly, open the MenuViewController.swift
file and delete the UITableViewDataSource
and UITableViewDelegate
extension codes that are at the bottom of the file.
Then, delete codes below from the configureTableView()
function. You won’t need these codes anymore.
tableView.delegate = self
tableView.dataSource = self
But since we deleted UITableViewDelegate
delegate, we need the add the code that specifies the height of the cells inside the configureTableView function. Thus, we need to add the code below.
tableView.rowHeight = 104
Now to connect the coffee data that will be in the menu to the table view reactively. In order for table view to be connected to these data reactively, our data construct needs to be reactive as well.
To ensure that, we need to make the coffees
variable Observable. We need to update the code as stated below.
.just(_:)
function shows that this Observable type variable will never vary. But even if our variable is invariable, we would be creating an Observable variable.
Note: Using the .just(_:)
method knowing the data will never change might seem meaningless but we need this to properly use both reactive programming technics and the power of Rx.
Add the code below to the top of the MenuViewController
class.
private let disposeBag = DisposeBag()
Add the code below to the viewDidLoad
method after configureTableView()
.
This code makes sure that the cells are added to table view should there be a change in the coffees variable. In our case, 5 coffee data that we defined to coffees
variable will be added to table view as cells.
Let’s see what these codes do step by step.
- We call the
bind(to:)
function to link this code which will run for every line in table view with thecoffees
variable. - We call the
rx
method to reach RxCocoa extensions. - We need to pass the cell identifier and the class of the cell that we want to use in the
items(cellIdentifier:cellType:)
method. This way, Rx framework calls the dequeuing methods that we normally created using delegate on its own. - This code block runs for each new element. It allows us to get row, element and cell information. In our case, the element variable is
Coffee
type. We configure our cells consisting of CoffeeCell by givingconfigure(with:_)
method the current coffee data. bind(to:)
function returns a Disposable type variable. We need to add it to thedisposeBag
of this class to properly dispose of the Observer we created when theMenuViewController
class deallocated.
In addition to this, Rx Framework will calculate the outputs of the tableView(_:numberOfRowsInSection:)
and numberOfSections(in:)
methods according to the observed variable automatically. Also, as you can see, we changed the tableView(_:cellForRowAt:)
method with the closure we wrote above.
Now, let’s run the application and check if everything is as they used to be. Not bad. Almost everything is as it was before but there’s something missing. The application doesn’t know what to do now since we deleted the tableView(_:didSelectRowAt:)
method.
To fix this, we need to use the modelSelected(_:)
method, another extension of RxCocoa for table view. This method returns the model of the selected (clicked) cell as Observable.
Let’s add the code below under the code we added above.
Let’s see what these codes do step by step:
- We call the
rx
method to access RxCocoa extensions. - This passes the type of the element that is going to return to the
modelSelected(_:)
method, reactive extension of table view, and returns the element selected from the table view as Observable. - We pass this Observable variable to the closure inside of the
subscribe(onNext:)
method. This closure runs when each cell is selected (clicked.) - We run the segue that we previously defined from Storyboard to go to the coffee detail page.
- We remove the line we selected from the table view from selected state.
- The
subscribe(onNext:)
method returns a Disposable type variable. We need to add it to thedisposeBag
of this class to properly dispose of the Observer we created when theMenuViewController
class deallocated.
Finally, our job is done with table view. Now, our table view is running completely reactively.
Let’s Make the Cart Button In the Navbar Reactive Too!
The number inside of the red circle in our button updates itself according to the product quantity in the cart. In our current construct, there’s a code particle that takes the total order count from ShoppingCart
model and updates the badge in the button in the viewWillAppear(:_)
method of MenuViewController
. If we change the coffees variable in our ShoppingCart
model to an Observable variable, we won’t always have to take the final state of the order count in the viewWillAppear(:_)
method. Thanks to reactive programming, we can ensure that the badge in our button is automatically updated every time a new element comes into the coffees
variable.
Let’s open the ShoppingCart.swift
file and change it with the code below.
Let’s take a look at what these codes do step by step:
- Here, for the first time, we use a type other than Observable. BehaviourRelay is a wrapper of BehaviourSubject. We can say that Subjects are proxies or bridges which can act as both Observable and Observer in the Rx concept. I will explain further these concepts in another article. But for now, what we need to know is: BehaviourSubject returns us the element it last emitted when we subscribe to a variable.
- When BehaviourSubject accesses the value parameter of a variable, we can get the element that’s been emitted last.
coffees.value
code returns us a[Coffee: Int]
type dictionary. We will assign this dictionary to a temporary variable and update the coffee that will be added. - We can emit a new element inside a BehaviourSubject type variable with the
accept(:_)
method. - Here, we take the last value inside the variable same as when we added, and assign it to the variable. Then, we remove the coffee we want to delete from the dictionary.
- We emit the dictionary we updated to the
coffees
variable. - We turn the
getTotalCost()
method into a method that returnsObservable<Float>
instead ofFloat
. - We mentioned that BehaviourRelay acts as both Observable and Observer. We need to take the price information of the elements inside the
coffees
variable and return their total. At the same time, we need to return it as an Observable variable. To do that, we can access the elements inside thecoffees
variable by mapping it and change it however we want. - We turn the
getTotalCount()
method to a method that returnsObservable<Int>
instead ofInt
. - We return the total amount of coffee in the cart as Observable.
- We turn the
getCartItems()
method to a method that returnsObservable<[CartItem]>
instead of[CartItem]
. - We return the CartItem model that we use in the table view at the basket page as an Observable array.
Our ShoppingCart
model is now reactive. However, if you want to run your project now, you will see that Xcode gives error in several places. You need to return the places where we read data from the ShoppingCart
model reactive as well in order to run your project successfully.
Let’s go back to MenuViewController.swift
file and delete the viewWillAppear(:_)
method completely.
Paste the code below to the bottom of the viewDidLoad
method.
Here, we subscribe to the getTotalCount()
named Observable<Int>
type method of our ShoppingCart
model. Now, whenever there’s a change in the coffees
variable, current sum information of the ordered coffees returns with totalOrderCount
variable.
Cart Page
This page contains the number and prices of the coffees that we select from the menu and add to our cart. There’s also the option to delete the wanted order from the cart. You can see the total amount to be paid at the bottom of the page as well. Whenever you delete an order, the amount updates itself.
Let’s open the ShoppingCartViewController.swift
file. We need to make the table view reactive here as well. I’m not going to repeat the things we learned on the menu page. I will only talk about how we can make the row/cell deletion, which we are going to learn now, reactively.
Let’s start with changing the code below with the current code.
Let’s see what these codes do step by step.
- When we pass and call the element that will return to the
modelDeleted(_:)
method, which is a reactive extension of table view, the method will return us the element that’s been deleted from table view as Observable. - We pass this Observable variable to the closure inside the
subscribe(onNext:)
method. - We delete the
coffee
object in ourCartItem
model that’s been deleted from ourShoppingCart
model as well. - The
subscribe(onNext:)
method returns us a Disposable variable. We need to add this todisposeBag
of this class to dispose of the Observer we created when theShoppingCartViewController
class deallocated.
Now, we have made almost all of our projects reactive. There’s only OrderCoffeeViewController.swift
file left. If you want to reinforce what we learned here, I’m giving you a homework: Make this page reactive.
If you like the article, please don’t forget to like and follow me.
You can reach me via gktggumus@gmail.com should you have any questions about RxSwift and RxCocoa.