RxSwift For Dummies šŸ¤ Part 3

Letā€™s learn about the next building block of RxSwift: Subjects

I think we can agree that when you have a reference to an Observable itā€™s output only. You can subscribe to itā€™s output, but you canā€™t change it.

A Subject is an output(Observable) but itā€™s also an input! That means that you can dynamically/imperatively emit new elements in a sequence.

let subject = PublishSubject<String>()

// As you can see the subject casts nicely, because it's an Observable subclass
let observable : Observable<String> = subject

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

// You can call onNext any time you want to emit a new item in the sequence
subject.onNext("Hey!")
subject.onNext("I'm back!")

onNext is the method you use to do the input.

It will of course print:

"Hey!"
"I'm back!"

Why do we need subjects? To easily connect the declarative/RxSwift world with the imperative/normal world. Subjects feel more ā€œnaturalā€ to programmers used to imperative programming.

In a perfect, clean RxSwift implementation you are discouraged to use subjects - you should have a perfect stream of observables. Letā€™s not bother our heads with it though, Iā€™ll explain that in a separate post, please use as much subjects as you want for now šŸ˜…

So thatā€™s basically it! Letā€™s learn to control subjects now.

HotšŸ”„ vs Coldā„ļø

I mentioned the HotšŸ”„ vs Coldā„ļø in the first part of the tutorial. Letā€™s get into it in more detail now, because subjects are actually the first hot observables weā€™re encountering.

As we already established, when you create/declare an Observable using create it wonā€™t execute until thereā€™s an observer that observes on it. Itā€™ll start execution at the same moment something calls subscribe on it. Thatā€™s why itā€™s called a coldā„ļø observable. If you donā€™t remember you can take a quick look at Part 1

A hotšŸ”„ observable will emit itā€™s elements even it if has no observers. And thatā€™s exactly what subjects do.

 let subject = PublishSubject<String>()
 let observable : Observable<String> = subject
 // the observable is not being observed yet, so this value will not be caught by anything and won't be printed
 subject.onNext("Am I too early for the party?")
 
 observable
     .subscribe(onNext: { text in
         print(text)
     })
     .addDisposableTo(disposeBag)
 
 // This is called when there's 1 observer so it will be printed
 subject.onNext("šŸŽ‰šŸŽ‰šŸŽ‰")
 

Pretty straightforward, huh? If you understood the cold observable in Part 1, hot observable should give you no problems as itā€™s more intuitive/natural.

Subject Types

There are three commonly used subject types. They all behave almost the same with one difference: each one does something different with values emitted before the subscription happened.

ā€¢ Publish Subject

As you could see in the experiment above the publish subject will ignore all elements that were emitted before subscribe have happened.

 let subject = PublishSubject<String>()
 let observable : Observable<String> = subject
 subject.onNext("Ignored...")
 
 observable
     .subscribe(onNext: { text in
         print(text)
     })
     .addDisposableTo(disposeBag)
 
 subject.onNext("Printed!")

You use it when youā€™re just interested in future values.

ā€¢ Replay Subject

Replay subject will repeat last N number of values, even the ones before the subscription happened. The N is the buffer, so for our example itā€™s 3:

let subject = ReplaySubject<String>().create(bufferSize: 3)
let observable : Observable<String> = subject

subject.onNext("Not printed!")
subject.onNext("Printed!")
subject.onNext("Printed!")
subject.onNext("Printed!")

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

subject.onNext("Printed!")

You use it when youā€™re interested in all values of the subjects lifetime.

ā€¢ Behavior Subject

Behavior subject will repeat only the one last value. Moreover itā€™s initiated with a starting value, unlike the other subjects.

let subject = BehaviorSubject<String>(value: "Initial value")
let observable : Observable<String> = subject

subject.onNext("Not printed!")
subject.onNext("Not printed!")
subject.onNext("Printed!")

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

subject.onNext("Printed!")

You use it when you just need to know the last value, for example the array of elements for your table view.

Binding

You can bind an Observable to a Subject. It means that the Observable will pass all itā€™s values in the sequence to the Subject

let subject = PublishSubject<String>()
let observable = Observable<String>.just("I'm being passed around šŸ˜²")

subject
    .subscribe(onNext: { text in
        print(text)
    })
    .addDisposableTo(disposeBag)

observable
    //Passing all values including errors/completed
    .subscribe { (event) in
        subject.on(event)
}
.addDisposableTo(disposeBag)

Thereā€™s sugar syntax to simplify it a little bit called bindTo:

let subject = PublishSubject<String>()
let observable = Observable<String>.just("I'm being passed around šŸ˜²")

subject
    .subscribe(onNext: { text in
        print(text)
    })
    .addDisposableTo(disposeBag)

observable
.bindTo(subject)
.addDisposableTo(disposeBag)

It will of course print I'm being passed around šŸ˜².

Warning

Binding will pass not only values, but also completed and error events on which case the Subject will get disposed and wonā€™t react to any more events - it will get killed.

Quick Example

Letā€™s modify the example from the first post a little.

import Foundation
import RxCocoa
import RxSwift

final class GoogleModel {
    
    let googleString = BehaviorSubject<String>(value: "")
    private let disposeBag = DisposeBag()
    
    func fetchNewString() {
        
        let observable = Observable<String>.create({ (observer) -> Disposable in
            
            let session = URLSession.shared
            let task = session.dataTask(with: URL(string:"https://www.google.com")! as URL) { (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 {
                        let googleString = NSString(data: data!, encoding: 1 ) as String?
                        //Emit the fetched element
                        observer.onNext(googleString!)
                        //Complete the sequence
                        observer.onCompleted()
                    }
                }
            }
            
            task.resume()
            return Disposables.create {
                task.cancel()
            }
        })
        
        // Bind the observable to the subject
        observable
        .bindTo(googleString)
        .addDisposableTo(disposeBag)
    }
}

As you can see we have a view model that exposes a googleString subject that view controllers can subscribe to. We bind the observable to the subject, so when a reply from the server comes itā€™ll emit itā€™s value and it will get passed in the subject.

Bonus: Variable

Thereā€™s one more thing missing if you want to totally violate the declarative nature of RxSwift: reading the last emitted value imperatively.

Thatā€™s where Variable comes in. Variable is just a simple wrapper over BehaviorSubject. Feel free to take a look yourself - the implementation is really easy. Itā€™s very handy.

Letā€™s say for example that we want to be able to access the ā€œcurrentā€ googleString at any time.

let googleString = Variable("currentString")

//Getting the value
print(googleString.value)

//Setting the value
googleString.value = "newString"

//Observing the value
googleString.asObservable()
    .subscribe(onNext: { text in
        print(text)
})
.addDisposableTo(disposeBag)

Youā€™ll learn to love it. Itā€™s like RxSwift in easy mode ā˜ŗļø

Seems easy, but thereā€™s a lot of traps to watch out for, so please refer to my next post: RxSwift Safety Manual


Michal Ciurus

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