Observables aka Sequences
Basics
The equivalence of observer pattern (Observable<Element>
sequence) and normal sequences (Sequence
) is the most important thing to understand about Rx.
Every Observable
sequence is just a sequence. The key advantage for an Observable
vs Swift's Sequence
is that it can also receive elements asynchronously. This is the kernel of the RxSwift, documentation from here is about ways that we expand on that idea.
Observable
(ObservableType
) is equivalent toSequence
ObservableType.subscribe
method is equivalent toSequence.makeIterator
method.- Observer (callback) needs to be passed to
ObservableType.subscribe
method to receive sequence elements instead of callingnext()
on the returned iterator.
Sequences are a simple, familiar concept that is easy to visualize.
People are creatures with huge visual cortexes. When we can visualize a concept easily, it's a lot easier to reason about it.
We can lift a lot of the cognitive load from trying to simulate event state machines inside every Rx operator onto high level operations over sequences.
If we don't use Rx but model asynchronous systems, that probably means that our code is full of state machines and transient states that we need to simulate instead of abstracting away.
Lists and sequences are probably one of the first concepts mathematicians and programmers learn.
Here is a sequence of numbers:
--1--2--3--4--5--6--| // terminates normally
Another sequence, with characters:
--a--b--a--a--a---d---X // terminates with error
Some sequences are finite and others are infinite, like a sequence of button taps:
---tap-tap-------tap--->
These are called marble diagrams. There are more marble diagrams at rxmarbles.com.
If we were to specify sequence grammar as a regular expression it would look like:
next* (error | completed)?
This describes the following:
- Sequences can have 0 or more elements.
- Once an
error
orcompleted
event is received, the sequence cannot produce any other element.
Sequences in Rx are described by a push interface (aka callback).
enum Event<Element> {
case next(Element) // next element of a sequence
case error(Swift.Error) // sequence failed with error
case completed // sequence terminated successfully
}
class Observable<Element> {
func subscribe(_ observer: Observer<Element>) -> Disposable
}
protocol ObserverType {
func on(_ event: Event<Element>)
}
When a sequence sends the completed
or error
event all internal resources that compute sequence elements will be freed.
To cancel production of sequence elements and free resources immediately, call dispose
on the returned subscription.
If a sequence terminates in finite time, not calling dispose
or not using addDisposableTo(disposeBag)
won't cause any permanent resource leaks. However, those resources will be used until the sequence completes, either by finishing production of elements or returning an error.
If a sequence does not terminate in some way, resources will be allocated permanently unless dispose
is called manually, automatically inside of a disposeBag
, takeUntil
or in some other way.
Using dispose bags or takeUntil
operator is a robust way of making sure resources are cleaned up. We recommend using them in production even if the sequences will terminate in finite time.
In case you are curious why Swift.Error
isn't generic, you can find explanation here.