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 to Sequence
  • ObservableType.subscribe method is equivalent to Sequence.makeIterator method.
  • Observer (callback) needs to be passed to ObservableType.subscribe method to receive sequence elements instead of calling next() 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 or completed 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.

results matching ""

    No results matching ""