As a developer, you may be used to dealing with asynchronous tasks all the time: handling UI events as tapped buttons, downloading web content, communicating with external devices, etc. There are a lot of ways to keep parallel tasks, like Notification Center, didSet observables, delegation, IBActions or closures. Although, using all this features usually gives you a lot of work and there is no guarantee that your code will be executed in the desired order.
RxSwift came as a reactive solution for dealing with all those asynchronous tasks and inherited from a .NET solution called Rx. It allows you to compose a sequence of tasks, share a mutable state of your application and transform all the data you are observing. But this article actually aims on describing the most important concept of the RxSwift framework to help everyone who have some issues with understanding its core idea: the so known observables.
An observable is nothing more than a way to keep track of a sequence, which may be of any type: integers, strings, or other kinds of objects. An observable listens to that sequence, and for each new element, it triggers a handling routine to proceed with the code with that new observed value. Since you can define all the observed sequence at once, you can think of it as an iteration process, where each value is notified to an observer entity. I know it is pretty confusing at first, but i will illustrate everything with some practical examples
Basic of Observables
An observable is a sequence of events notifying another object that subscribes it to receive notifications of new values. It has a lifecycle beginning with its creation and finishing with a completed notification or an error. Before ending, snapshots of the observed value might be triggered, but only inside of its lifecycle. No event can be emitted after the observable's end.
Take a look at the image above. Observe that since its creation, the observable emits snapshots of the observed value which are handled by another object. Its value changes between x, y and z. Then, it is finished with a completed event or an error, after it, the observable object is disposed. Let's consider a practical example:
What is happening above is an iteration of three integers, respectively one, two and three. We have defining all the sequence that is being observed, and for each element, the entity that is subscribing the observable will print the notified event. Subscribing is a way of handling each new event triggered by the observable. In this case, the console will print:
next(1) //First element
next(2) //Second element
next(3) //Third and last element
completed //As the sequence is over, the observable is completed
There is another way of subscribing the sequence by defining each closure for a next event, a completed event and an error:
Notice that now we separated all the callbacks into types of event, in the other case we were printing the whole event object by itself, now we are printing the specific element for a Next event, the error for an Error event, and handling a Completed event. There is still a fourth one that we haven't discussed yer, the disposal.
But first, we need to show the other ways of defining an observable:
- Observable with only one element:
2. Observable from an array:
3. Observable of array type
4.Observable from a range of elements
The fourth event of an observable lifecycle that we didn't discuss is the Disposal. Different from pointers, Xcode is not responsible for disposing the subscriptions of an observable type. That means every time we are not using an observable anymore, the subscriptions to it are still hanging there and it may result in a memory leak.
There are some ways of dealing with it, and the first one is calling a disposing method just when we don't want the subscriptions:
At this example, we are telling the subscription to dispose just when we don't need it anymore. You must use it only when you really don't want subscriptions.
The second way is through disposing bags. A disposing bag is an object just like any other that only does the job of disposing a subscription when it is not needed. You must define the disposing bag of that subscription right after defining it:
The cases we discussed only give all the observed sequence by hand not allowing other external sources to emit those events. For instance, if we are downloading a video from the web, we are going to answer for the downloading thread and for each iteration of the task, like downloaded 30%, we must emit the next event by hand to notify the subscribers.
Take a look:
Look that in this example, we are emitting the events by hand with the two words "Hello" and "World", so there is no predefined sequence in this case and we can rely in parallel tasks to emit events. You must also notice that the create method takes as parameter a closure that returns a disposable. There must need a way to tell that we already finished emitting elements and all we need is to dispose anything subscribing to it. For that purpose, we are returning a disposable type.
We illustrated the very standard lifecycle of a RxSwift Observable type, but if I told you that it might be customised and listened as a different asynchronous routine? What about if we only care about its finishing, or error events, does not matter the elements being emitted?
Rx provides us a way to use different kinds of observables that only notify completed events or either have a different cycle. We are going to bring up three of them:
- Single: This kind of observable relies only in finishing events, which means, success or error. Single can result in a success on obtaining a value or a generic error, so this is how its lifecycle works:
An example of a scenario for single is downloading a file. It may result two possible major results: a success containing the file data or an error. Please take a look at that procedure:
We are requesting data from a Github repository and if there is an error with data task, it finishes with the respective error. In the other case, it finishes successful with the parsed JSON.
2. Completable: This variance of observable only ends with a specific error or a completed event, emitting no value. It is used when the task does not notify a value to the observer:
3. Maybe: This one is half way between the past two. It can only end with an error, a completed event with no value or even a successful event with some wrapped value.
There are some types of observables which can emit events out of its creation scope, working both as an observable and an observer. The subjects can either receive events or subscribe to them. There are four kinds of subjects, which are differentiated by how their subscribers are notified by their events:
Its subscribers are only notified by new events, and when their start the subscription, nothing is showed to them:
Different from the published ones, the behaviour subjects subscribers get the last notified value when they subscribe even without being there when it was triggered. For example, if s1 subscribes the subject and the latest emitted value was 3, it will get a next event since the beginning with that value
In the example, you can see that you initialise the subject already with a default value, which will be notified to its first subscriber. In the example, s1 will catch 1, 2 and 3, and s2 will catch only 2 and 3 since 2 was the last value before its subscription.
They work much like the behaviour subjects, although instead of notifying new subscribers only with the latest value, it holds a fixed-size buffer with a constant number of elements to notify new subscribers.
For instance, if the buffer length is 3 and the values 1,2,3,4,5 were observed, and a new subscriber takes action, 3,4 and 5 will be notified to it. Take a look:
In the last example, we trigger three values to the subject, and when a new subscriber enters the scene, it prints the latest 3 ones, respectively 1,2,3. When the second subscriber appears, it prints 3,4 and 5.
This is a customised subject that contains a behaviour subject behind the scenes. This one only holds a value defined as a variable, and keeps changing it just like any standard variable.
We just keep changing its value without using the onNext method. It also does not emit any kind of error and when subscribed neither a completed event, supposed to be always succeeded. It must be cast asObservable() to work.
Like the behaviour subject, it gives the latest value to new subscribers. Take a look at an example:
It works just like the behaviour subject, s1 will print the first value 1, then the others when emitted. S2 will print from the latest one before the subscription.
What about if we don't want to deal with the exact type of observed value we have? If we are observing a sequence of integers and we want to transform them into strings for manipulation? Or if we want only some specific values, how do we filter?
As we are only giving a brief introduction to observables concept, we will only cover the types of operators we can have, leaving the specific ones to a later article.
Filters work very well when we don't want to be notified of every new item in a sequence , but only with specific properties. For example, if we are only interested in even numbers, we must furnish a closure with a predicate to be filtered:
Only even numbers will be printed on the console.
If you are observing an integer sequence but you actually need strings, you can use a mapping function on the observed values to transform them into strings:
As you can see, it is taking each value of the observable and formatting into a proper string.
This article serves to clarify the core ideas behind observables, which is the main concept behind the RxSwift and other reactive technologies. We covered about how to set up observables, how to create and subscribe observers to them, customised observables and their lifecycles, subjects and filtering and transforming operators to observed sequences. There is a lot more to talk about, but I hope you got the main idea and became more interested about how the reactive paradigm can help you with asynchronous tasks in your applications. See you in the next article ;)