Intro to RxSwift (part 2)

Hi there, this is a continuation of my previous post dedicated to RxSwift. As I told you, you’ll need to know a few things to understand Reactive Extensions: Observables, Subscriptions, Subjects, Operators, Schedulers. We already covered Observables and Subscriptions, now are going to talk about Subjects. If you never read that article I encourage you to do that.

Subjects:

We already learned about observables and observers(subscriptions). But probably, you had a thought that observables are kind of  “straightforward” and you can not add events to observables stream once you create them. So here comes the Subjects. Subjects act as both an observable and an observer, where you can add events to an existing observable stream. And there are 4 types of Subjects which we going to cover: PublishSubject, BehaviorSubject, ReplaySubject, Variable.

PublishSubject

Publish subjects used when you want subscribers to be notified only of new events from the point at which they subscribed. By saying new events I mean only new events. Even if observable emitted events before and someone subscribed to it at some point, it won’t get previous events, only wait for new events. Here is some code to try:

let publish = PublishSubject<Int>()
let subscription1 = publish.asObservable().subscribe {print("Subscription 1: \($0)")}
publish.onNext(10)
let subscription2 = publish.asObservable().subscribe {print("Subscription 2: \($0)")}
publish.onNext(20)
let subscription3 = publish.asObservable().subscribe {print("Subscription 3: \($0)")}
publish.onNext(30)

We create PublishSubject publish and create subscription1 for it. And then we add value 10 to publish observable stream. Next, we create subscription2 for publish observable. And again add a new value 20 to publish observable stream. Guess what is the output?

Subscription 1: next(10)
Subscription 1: next(20)
Subscription 2: next(20)
Subscription 1: next(30)
Subscription 2: next(30)
Subscription 3: next(30)

We can see that subscription2 did not get the previous value 10, but it received all new coming values. Pretty easy. The very common case for using it in real app development is handling button taps. PublishSubject observes button touches, and subscribers will get only new touches because most likely they do not need to know if the button was tapped before.

BehaviorSubject

BehaviorSubject almost the same as PublishSubject, with one exception: new subscribers will get the last emitted event.

let behavior = BehaviorSubject<String>(value: "Hi")
let subscription1 = behavior.asObservable().subscribe {print("Subscription 1: \($0)")}
behavior.onNext("How are you?")
let subscription2 = behavior.asObservable().subscribe {print("Subscription 2: \($0)")}
behavior.onCompleted()
let subscription3 = behavior.asObservable().subscribe {print("Subscription 3: \($0)")}
behavior.onNext("Hey")

We use almost the same example we did with PublishSubject, but this time using String values. And since BehaviorSubject always returns last emitted values to new subscribers it has to have the initial value. In this example, we create BehaviorSubject with the initial OnNext event with the String value “Hi”. Then we create subscriptions for that subject. As you can see besides onNext event, we used the onCompleted event which completes the stream. And after that, we tried to emit the new onNext event. You can guess what the output will be.

Subscription 1: next(Hi)
Subscription 1: next(How are you?)
Subscription 2: next(How are you?)
Subscription 1: completed
Subscription 2: completed
Subscription 3: completed

So, this time new subscribers got previous values at the point of subscription. subscription1 got the initial value of the behavior, as well as next subscriptions, got the event the observable emitted before. And also, as you can see once subject emitted the onCompleted event, the stream terminated and all subscriptions stopped receiving any events. That’s why we didn’t get onNext(“Hey”) event.

The good example in real app development would be observing a text entry by BehaviorSubject and have multiple subscriptions to work with that text.

Variable

The Variable wraps a BehaviorSubject and stores its current value as a state. Which means you can access the last emitted value of the subject without subscribing to it, just by calling value property of the subject. That value got from the unwrapped onNext event, which means Variable is guaranteed not to emit the onError event. And you can not manually push onCompleted event, only if a variable is deallocated.

let variable = Variable<Bool>(false)
let subscription1 = variable.asObservable().subscribe {print("Subscription 1: \($0)")}
variable.value = true
print("I just printed that variable is \(variable.value)")

We create a Variable of type Bool with the initial value of “false”. Then subscribe to that variable and print some text. Next, we change the value of the variable, note that subscriber gets that change. And finally, we print some text access the value of the variable.

Subscription 1: next(false)
Subscription 1: next(true)
I just printed that variable is true

As you can see Variable is convenient using it both as Observable and as regular value. You might be using this type of subject a lot.

ReplaySubject

ReplaySubject is quite a simple one. Remember that BehaviorSubject returns only one last emitted event? So replay subject almost the same with one small difference, you can adjust the number of last events you want to get. And think about the buffer when we talking about that.

let replay = ReplaySubject<Int>.create(bufferSize: 3)
let subscription1 = replay.asObservable().subscribe {print("Subscription1: \($0)")}
replay.onNext(0)
replay.onNext(5)
replay.onNext(9)
replay.onNext(8)
let subscription2 = replay.asObservable().subscribe {print("Subscription2: \($0)")}

Here we create a ReplaySubject of type Int with buffer size 3. Then we create subscription1 for it, at this point observable didn’t emit any events, and subscription1 will get all future events. We push some events to replay subject and create a new subscription. So guess what values will subscription2 print?

Subscription1: next(0)
Subscription1: next(5)
Subscription1: next(9)
Subscription1: next(8)
Subscription2: next(5)
Subscription2: next(9)
Subscription2: next(8)

Subscription1 gets all events even if the number of events is less than the buffer size. And Subsciption2 gets only last 3 events, it didn’t print next(0) event. This subject is useful when you need not only last value but also values before last.

Disposables

In my first article about RxSwift, you probably noticed weird word Disposable in the example code. So as I promised you, I’d like to explain to you what Disposables are before moving forward. So, the word disposable speaks for itself. The purpose of disposing to release subscriptions out of memory. And even if Observable is still alive and emits some evens, disposed subscriptions will not get that event. The easiest way to deallocate subscription is to call Dispose method.

let behavior = BehaviorSubject<Int>(value: 0)
let subscription = behavior.asObservable().subscribe { print($0) }
behavior.onNext(5)
subscription.dispose()
behavior.onNext(10)

We create a BehaviorSubject and subscription to it. Push some value to the stream, and then we Dispose the subscription. And push some value again. The output:

next(0)
next(5)

So, as you can see it didn’t print 10 value because the subscription was deallocated. And if you need to perform actions on deallocation, subscribe(onDespose: ) can handle that.

let publish = PublishSubject<String>()
let subscription = publish.asObservable().subscribe(onNext: { (value) in
    print(value)
}, onDisposed: {
    print("The subscription was disposed")
})
publish.onNext("Hi")
subscription.dispose()
publish.onNext("How are you?")

We create PublishSubject and subscription to that subject where we sort events by onNext and onDisposed event. We push onNext event and dispose the subscription. The output is:

Hi
The subscription was disposed

And here as well it didn’t print any values after disposing.

You are probably wondering about the case where you will have a lot of subscriptions, and would that mean that you have to dispose every single of them? The answer is no. RxSwift provides a powerful functionality to manage that. It is called DisposeBag. You will put your subscriptions into DisposeBag, and once your dispose bag deallocates all subscriptions in it deallocates too. Here is the simple example of it. Usually, you would handle releasing action callbacks from the array in some weird way. I would like to show two versions of code without and with RxSwift, and power of Disposables.

class Cat {
    init() {
        Person.shared.onEnter {[weak self] in
            guard let cat = self else { return false }
            print("Meaw")
            return true
        }
        Person.shared.entered = false
    }
}

class Person {
    static let shared = Person()
    typealias Callback = () -> Bool
    private var enterCallbacks: [Callback] = []
    var entered: Bool = false {
        didSet {
            for i in (0 ..< enterCallbacks.count).reversed() {
                if !enterCallbacks[i]() {
                    enterCallbacks.remove(at: i)
                }
            }
        }
    }

    func onEnter(callback: @escaping Callback) {
        enterCallbacks.append(callback)
    }
}

if true {
    let cat = Cat()
}
Person.shared.entered = false

We have 2 classes Cat and Person. Cat as usually does nothing, except making “Meaw” sounds once a person comes in. And Person has entered property which informs if Person entered the room. Also, it has private callbacks which execute on entered value change, callback returns a Bool value which tells if the callback has to be released from memory. This is the nasty mechanism of releasing callback from memory, but it’s a common practice. With RxSwift you can handle it with DisposeBag.

class Cat {
    let bag = DisposeBag()
    init() {
        Person.shared.entered.asObservable().subscribe(onNext: {[weak self] (value) in
            print("Meaw")
        }).disposed(by: bag)
    }
}

class Person {
    static let shared = Person()
    let entered = Variable<Bool>(false)
}

if true {
    let cat = Cat()
}
Person.shared.entered.value = true

First, you can see it takes less code, to perform the same task. Second, it’s optimized because in previous example callbacks did not release from memory until they execute. So, once the Cat object deinits subscription will be destroyed too.

In the next article, we are going to cover a huge and important theme: Operators. So subscribe to be notified of new upcoming articles.

I’ve submitted the source code with all example to github: https://github.com/Utemissov/RxSwift-Subjects

Where to go from here:

The most resourceful place about RxSwift is https://github.com/ReactiveX/RxSwift. So go there try their examples and get deep into documentation.

 

See you in my next blog post about RxSwift where I will tell you about Operators. 

Leave a comment if you have any questions, or just want to say “Hi”, I will be glad to talk.

Subscribe to get notified of new posts.

Thank you

 
Loading
Please follow and like us:

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.