First of all, let’s start with some explanations. There are dozens of concepts in nowadays programming emerging each week, and every single one promises some salvation. They all have their prophets while profits aren’t clear sometimes. So what is reactive programming, what benefits its adoption can bring to developers and projects and how does rxswift reactive programming looks like.
What is it?
There is quite a straightforward explanation: “Reactive programming is programming with asynchronous data streams”. Pretty much a familiar idea, since click events flow is in fact an asynchronous data stream, but with Reactive Programming everything is pumped up greatly. How? With Reactive, almost anything can become a stream. That could be user inputs, data structures or variables, the other day. For instance any social media newsfeed, say Facebook or Twitter, might as well be treated as a data stream, that could be monitored (so-called “subscription”) and a handful of various reactions to that is at one’s disposal. Moreover, ReactiveX - “An API for asynchronous programming with observable streams” - has a tremendous collection of tools for handling those streams. Streams could be merged, filtered, combined and data values could be mapped as an input for the new stream, or used as new streams themselves.
Why is all that important?
The best answer are almost any modern mobile application and, to some extent, weapps. Their interactivity levels are unprecedented, especially in iOS, where smoothness is kind of a fetish, the real-time user experience is considered one of the main goals. And since Swift is Apple’s main programming language right now, the weapon of choice for Reactive Programming in Swift is RxSwift. See, mobile development these days has to deal with multiple UI events that might also have data events connected, and that smooth, highly interactive UI experience requires a massive amount of those. And then the development complexity comes (and development cost follows), and boilerplate code is no longer acceptable, so a new tool is required. FRP is the answer.
Streams
Streams - time-ordered event sequences - are fundamental for reactive. Monitoring them - which is called “subscribing” - is awaiting for one of the three events that a stream can emit:
1. A typed value
2. An error
3. A “completed” signal (app or page closure, for example).
And it is not just awaiting the events, what matters is assigning dedicated functions - the “observers” - to corresponding events.
Reactive programming with rxswift starts with its basic building block - the Observable - the watched or “observed” stream. It works in order for observers to react. Observables can be cold - starting their work only when Observers are present, or hot - those working no matter someone’s watching.
RxSwift’s mechanism called “Schedulers” provides a flexible way of controlling threads and queues for both Observables and Observers to execute. If none (Scheduler) used, the Observable will execute it’s code on the main thread, thus requiring a “dispatch_async(…)” in order for the main thread not to be blocked.
Mainly two operators are used in the Schedulers’ area:
• ObserveOn - defines a scheduler for the events to be send from the Observable to the Observer. The scheduler (thread/queue) on which the Observable is executed remains unchanged.
• SubscribeOn - similar to the previous one, but also changing the scheduler (thread/queue) on which the Observable is executed.
Each Observable ought to return a Disposable, that is used to clean up the Observable in case it won’t complete its work properly.
NopDisposable.instance is used if there is nothing to dispose. NopDisposable implementation is just empty methods, it really is harmless, because it literally does nothing.
Let us look at some of Observable operators, please refer to ReactiveX official documentation for the full list of operators. We’ll look at four of them:
1. Create
2. Just
3. Interval
4. Repeat
The first one, Create, is obviously a way to create an Observable. It is an advanced way that creates Observables from scratch and is recommended for complicated cases, since it has total control on Observable parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
example("create") { let lettersSet= Observable<AnyObject>.of(“c”,”a”,”l”,”l”) let numbersSet = Observable<AnyObject>.of(9,1,1) let resultSet = Observable<Observable<AnyObject>>.create { observer in observer.on(.Next(lettersSet)) observer.on(.Next(numbersSet )) return NopDisposable.instance } let concatenatedSet = resultSet.concat() concatenatedSet .subscribe { element in print(element) } } |
Here we’ve manually created couple of Observables, one containing letters and other containing numbers, then concatenated them into a new one and subscribed to it.
Just creates an observable that emits only one value only one time and completes.
1 2 3 4 5 6 7 8 9 |
example("just") { let jSample = Observable<String>.just("only value"); jSample.subscribe { element in print(element) }.addDisposableTo(disposeBag) jSample.subscribeCompleted { print("complete") }.addDisposableTo(disposeBag) |
Interval - a quite straightforward operator. It increments an Int from 0 every N seconds. Threading/async behavior is managed by the scheduler.
1 2 3 4 |
let sampleInc = Observable<Int>.interval(N, scheduler: MainScheduler.instance) observable.subscribeNext { (element) in print(element) }.addDisposableTo(disposeBag) |
Repeat - well, it repeats (sure!) a given value infinitely.
1 2 3 4 |
let sampleRepeat = Observable<String>.repeatElement("Value") observable.subscribeNext { (element) in print(element) }.addDisposableTo(disposeBag) |
Now three methods concerning Observers.
1. observer.onNext(…)
2. observer.onCompleted(…)
3. observer.onError(…)
The first one is used to emit values within the lifetime of the Observable, other two are used to handle exceptions and natural end of stream, after their execution, the Observable can no longer emit any events.
There are two more powerful Observable operators we’d like to look at. They are Map and Scan.
Map operator maps an Observable according to a programmer controlled rule, a different type is emitted. In this example we map positive Int to true and negative Int to false.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
let mapSample = Observable<Int>.create { (observer) -> Disposable in observer.onNext(99) return NopDisposable.instance } let boolmapSample : Observable<Bool> = mapSample.map { (element) -> Bool in if (element < 0) { return false } else { return true } } boolmapSample.subscribeNext { (boolElement) in print(boolElement) }.addDisposableTo(disposeBag) |
Scan is a bit more complex. It allows changes to the current element based on the previously emitted one and is also able to accumulate elements. The seed or starting value is passed as a parameter to the scan. This example will calculate a factorial of 10 (10!=3628800 it will print only the result):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
let observable = Observable<Int>.create { (observer) -> Disposable in observer.onNext(1) observer.onNext(2) observer.onNext(3) observer.onNext(4) observer.onNext(5) observer.onNext(6) observer.onNext(7) observer.onNext(8) observer.onNext(9) observer.onNext(10) return NopDisposable.instance } observable.scan(1) { (lastValue, currentValue) -> Int in return lastValue * currentValue }.subscribeNext { (element) in print(element) }.addDisposableTo(disposeBag) } } |
These examples and the text above are intended to show the RxSwift’s great advantage: Functional Reactive Programming with RxSwift eases the cognitive loads and raises abstract levels, that frees the team so it could concentrate on business logics and proper app architecture without digging themselves down into boilerplate code. And the toolbox is enormous and “how to use” docs are emerging rapidly. And then there is RxSwift MVVM. It all makes RxSwift a much preferred tool for modern iOS development. And also there is a glorious bunch of ReactiveX users, quite a company: