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.