RxSwift For Dummies šŸ£ Part 1

Edit 02.02.2017: This post was updated to Swift 3.0 and RxSwift 3.2

RxSwift is one of those things you have to try yourself to really start appreciating. Itā€™s the one piece of the puzzle that I was missing to glue all the patterns like MVVM, VIPER, Coordinators/Routing.

Itā€™s hard to express what RxSwift really is, because it does a lot. In general it serves as a great observer pattern with a mix of functional and reactive programming. Itā€™s important to say what it isnā€™t and it isnā€™t Functional Reactive Programming in itā€™s original definition. Itā€™s heavily inspired on FRP, so we can say that it contains Reactive and Functional features.

If you donā€™t know what FRP is, donā€™t worry for now - youā€™ll be able to discover it yourself in this tutorial. Youā€™ll gradually learn what Functional and Reactive mean in the FRP.

Digging through RxSwift made me feel enlightened and saved, but also massively confused. Trust me, youā€™ll feel the same.

It takes a couple of hours to get used to the idea, but when you do, you donā€™t want to go back.

In this tutorial, Iā€™ll try to save you these precious hours by explaining everything step by stepā€¦ You know, like to a dummy šŸ˜œ

You just need some decent knowledge of Swift and UIKit.

The Why?

UI programming is mostly about reacting to some asynchronous tasks. Weā€™re taught to implement that with observer patterns: Iā€™m pretty sure youā€™re familiar with delegates by now. Delegating is a cool pattern, but it gets really tiringā€¦

Crying

  • Delegating is a lot of boilerplate code: creating a protocol, creating a delegate variable, implementing protocol, setting the delegate
  • The boilerplate reptition often makes you forget things, like setting the delegate (object.delegate = self)
  • The cognitive load is quite high: it takes quite a lot of jumping through files to find out whatā€™s what

RxSwift takes care of that and more! It enables you to create observer patterns in a declarative way (reduces cognitive load) and without any boilerplate code.

Iā€™ve just started a project, I didnā€™t create one delegate.

Basic Example

Ok, enough talking, letā€™s get to it, but letā€™s start simple.

class ExampleClass {
    let disposeBag = DisposeBag()
    
    func runExample() {
        
        // OBSERVABLE //
        
        let observable = Observable<String>.create { (observer) -> Disposable in
            DispatchQueue.global(qos: .default).async {
                Thread.sleep(forTimeInterval: 10)
                observer.onNext("Hello dummy šŸ£")
                observer.onCompleted()
            }
            return Disposables.create()
        }        
        // OBSERVER //
        
        observable.subscribe(onNext: { (element) in
            print(element)
        }).addDisposableTo(disposeBag)       
    }
}

Hereā€™s a basic example. We have a runExample method that does something with RxSwift when called. Letā€™s try to understand whatā€™s happening here.

Observable šŸ“”

Letā€™s start from the basic building block in RxSwift: the Observable. Itā€™s actually pretty simple: the Observable does some work and observers can react to it.

let observable = Observable<String>.create { (observer) -> Disposable in
    
    DispatchQueue.global(qos: .default).async {
        // Simulate some work
        Thread.sleep(forTimeInterval: 10)
        observer.onNext("Hello dummy šŸ£")
        observer.onCompleted()
    }
    return Disposables.create()
}

observable.subscribe(onNext: { (element) in
print(element)
}).addDisposableTo(disposeBag)

Ok, we have an Observable. This is a cold ā„ļø observable: it will start executing only when an observer subscribes. A hot šŸ”„ observable executes even if it doesnā€™t have any observers.

Weā€™ll cover the difference and examples in the next parts, so donā€™t worry, but for now you have to understand that: the Hello dummy šŸ£ value will not be emitted just because you instantiated an ā„ļøObservable object. The ā„ļøObservable is frozen and will start executing only when you add an observer.

Letā€™s analyze step by step whatā€™s happening:

DispatchQueue.global(qos: .default).async {...}

The Observable executes code on the main thread (unless programmed otherwise) so letā€™s use a simple DispatchQueue to not block it. RxSwift has a mechanism called Schedulers that we could use instead, but letā€™s leave that for later when youā€™re less of a dummy šŸ”.

observer.onNext("Hello dummy šŸ£")

An Observableā€™s time of work is also called a sequence. Throughout itā€™s sequence it can send an infinite number of elements and we use the onNext method to emit these.

observer.onCompleted()

When itā€™s finished it can send a Completed or Error event, after which it cannot produce more elements and it releases the closure along with itā€™s references.

return Disposables.create()

Each Observable has to return a Disposable.

Use Disposables.create() if you donā€™t need to dispose of anything. If you look into the NopDisposable implementation it does completely nothing - just empty methods.

Disposable

The Disposable that needs to be returned in the Observable is used to clean up the Observable if it doesnā€™t have a chance to complete the work normally. For example you can use the AnonymousDisposable:

return Disposables.create(with: {
    connection.close()
    database.closeImportantSomething()
    cache.clear()
})

The Disposable is called only when an Observer is disposed of prematurely: when it gets deallocated, or dispose() is called manually. Most of the times the dispose() is called automatically thanks to Dispose Bags. A little lost? Donā€™t worry, youā€™ll be able to implement that yourself on a more concrete example.

Observer šŸ•µ

Our Observable is cold ā„ļø. It wonā€™t start executing until we start observing it.

let disposeBag = DisposeBag()

...

observable.subscribe(onNext: {(element) in
  print(element)
}).addDisposableTo(disposeBag)

Thatā€™s the way you subscribe. Subscription is created and a Disposable (a record of that subscription) is returned by the subscribeNext method.

The Observable has started work and after 10 seconds youā€™ll see this printed out:

Hello dummy šŸ£

subscribe(onNext:) will only react to Next events. You can also use subscribe(onCompleted:) and subscribe(onError:).

Dispose Bag šŸ—‘

The only cryptic thing here is the addDisposableTo method.

Dispose bags are used to return ARC like behavior to RX. When a DisposeBag is deallocated, it will call dispose on each of the added disposables.

You add the Disposables you create when you subscribe to the bag. When the bagā€™s deinit is called (for example when the ExampleClass object gets deallocated) the Disposables (subscriptions) that didnā€™t finish will be disposed of.

Itā€™s used to dispose of old references that you pass in the closure and resources that are not needed anymore: for example an open HTTP connection, a database connection or a cache.

If you donā€™t understand now, donā€™t worry, a better example is coming.

Observable operators

create is just one of many ways to create an Observable. Take a look into ReactiveX official documentation for a list of operators. Letā€™s take a look at some of them.

Just

let observable = Observable<String>.just("Hello again dummy šŸ„");
observable.subscribe(onNext: { (element) in
    print(element)
}).addDisposableTo(disposeBag)
        
observable.subscribe(onCompleted: { 
    print("I'm done")
}).addDisposableTo(disposeBag)
Hello again dummy šŸ„
I'm done

just just creates an observable that emits one value once and thatā€™s it. So the sequence in that example would be: .Next("Hello") -> .Completed

Interval

let observable = Observable<Int>.interval(0.3, scheduler: MainScheduler.instance)
observable.subscribe(onNext: { (element) in
   print(element)
}).addDisposableTo(disposeBag)
0
1
2
3
...

interval is a very specific operator that increments an Int from 0 every 0.3 (in this example) seconds. The scheduler is used to define the threading/async behavior.

Repeat

let observable = Observable<String>.repeatElement("This is fun šŸ™„")
observable.subscribe(onNext: { (element) in
   print(element)
}).addDisposableTo(disposeBag)
This is fun šŸ™„
This is fun šŸ™„
This is fun šŸ™„
This is fun šŸ™„
...

repeat repeats a given value infinitely. Again, you can control the threading behavior with a SchedulerType.

As you probably noticed, these are not very exciting, but itā€™s good to know that there are other operators. One more important thing to notice is that itā€™s a start of the functional part of RxSwift.

Real life example

Ok, letā€™s wrap this up and letā€™s do a quick example. Our knowledge of RxSwift is quite limited, so letā€™s use a simple MVC case. Letā€™s create a model that will create an Observable that fetches data from google.com. Fun! šŸŽ‰

import Foundation
import RxCocoa
import RxSwift

final class GoogleModel {
    
    func createGoogleDataObservable() -> Observable<String> {
        
        return Observable<String>.create({ (observer) -> Disposable in
            
            let session = URLSession.shared
            let task = session.dataTask(with: URL(string:"https://www.google.com")!) { (data, response, error) in
                
                // We want to update the observer on the UI thread
                DispatchQueue.main.async {
                    if let err = error {
                        // If there's an error, send an Error event and finish the sequence
                        observer.onError(err)
                    } else {
                        if let googleString = String(data: data!, encoding: .ascii) {
                            //Emit the fetched element
                            observer.onNext(googleString!)
                        } else {
                            //Send error string if we weren't able to parse the response data
                            observer.onNext("Error! Unable to parse the response data from google!")
                        }
                        //Complete the sequence
                        observer.onCompleted()
                    }
                }
            }
            
            task.resume()
            
            //Return an AnonymousDisposable
            return Disposables.create(with: {
                //Cancel the connection if disposed
                task.cancel()
            })
        })
    }
}

Thatā€™s pretty simple: the createGoogleDataObservable creates an Observable we can subscribe to. The Observable creates a data task and fetches the google.com website.

DispatchQueue.main.async {...}

The data task of URLSession is executed on a background thread, so we need to update Observers on the UI queue. Remember that we could use schedulers, but Iā€™ll cover that in a more advanced stage.

return Disposables.create(with: {
 task.cancel()
})

The Disposable is a great mechanism: if the observer stops observing the data task will be cancelled.

Now the observer part:

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {
    
    //The usual way to create dispose bags
    //When the view controller is deallocated the dispose bag
    //Will be released and will call dispose() on it's Disposables/Subscriptions
    let disposeBag = DisposeBag()
    let model = GoogleModel()
    
    @IBOutlet weak var googleText: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Remember about [weak self]/[unowned self] to prevent retain cycles!
        model.createGoogleDataObservable()
            .subscribe(onNext: { [weak self] (element) in
                self?.googleText.text = element
            }).addDisposableTo(disposeBag)
        
    }
}

Amazing, huh? No protocols, no delegates, just a declarative definition of what should happen when thereā€™s a new event.

Donā€™t forget about [weak self] or [unowned self] in the closure to avoid retain cycles.

Thereā€™s a more reactive way to implement setting the text thatā€™s called binding, but weā€™re too šŸ£ to get into that now.

Dispose Bag Example

As you mightā€™ve noticed, the disposeBag is an instance variable of our ViewController:

class ViewController: UIViewController {
    
let disposeBag = DisposeBag()

When the view controller gets deinitialized, it will also release the disposeBag.

If the disposeBag is released, itā€™s deinit will be called and our AnonymousDisposable (created using Disposables.create(with:)) will be called on our Observable and the data task will be cancelled and the connection will be closed if it didnā€™t have a chance to finish!

I hope this clearly explains the mechanism of dispose bags.

Thatā€™s it!

Hereā€™s the code for the example project.

This wraps it up. Youā€™ve learned how to create observables and observers, how disposing works and hopefully you can see how this is better than the usual observer patterns.

Part 2 is about the whole functional part in RxSwift - operators.

Update: This post was updated to Swift 3.0 & RxSwift 3.2 by Yogish


Michal Ciurus

A passionate iOS dev always trying to get to the bottom of stuff.