RxSwift is a project I have been following on Github for a long time. Today I spent some time going through its sample code and found it very interesting. I mainly learned and understood it through Rx.playground in the project. This method is indeed convenient and efficient. You only need to comment the document with /*: */ and write it directly in Markdown, which is simple and convenient. However, this method is not very stable in Xcode7. There will be a lot of blank lines, and there is a big problem. When you read to the middle and then switch to other files and then switch back, the reading progress bar starts from the beginning and cannot record the last reading position. It is tiring. Below is my simple notes, just recording what I learned during the learning process. Most of the content comes from the playground in the project. Note! It is a large part ! And the playground is well illustrated and easy to understand. So, if you are interested, I suggest you clone the official project and play around in the playground. The references list the relevant materials I consulted during the learning process, which can be used as supplementary reading. SupportCode Before we get into the topic, let’s take a look at SupportCode.swift in the project, which mainly provides two convenience functions for the playground. One is the example function, which is used to write sample code, and outputs logs uniformly for easy tagging and browsing, while keeping variables from polluting the global environment: - public func example(description: String, action: () -> ()) {
- print( "\n--- \(description) example ---" )
- action()
- }
The other is the delay function, which is used to demonstrate the delay through dispatch_after : - public func delay(delay:Double, closure:()->()) {
- dispatch_after(
- dispatch_time(
- DISPATCH_TIME_NOW,
- Int64(delay * Double(NSEC_PER_SEC))
- ),
- dispatch_get_main_queue(), closure)
- }
Introduction This article mainly introduces the foundation of Rx: Observable . Observable<Element> is the observed object in the observer pattern, which is equivalent to an event sequence ( GeneratorType ) and sends newly generated event information to subscribers. Event information is divided into three types: -
.Next(value) indicates new event data. -
.Completed indicates the completion of the event sequence. -
.Error also indicates completion, but it represents completion caused by an exception.
(A side note: The naming of the protocol reminds me of what Tom said on Weibo this morning: In addition, I think it is more semantically clear to use adjectives in the protocol name, such as Swift: Flyable, Killable, Visible. Using all nouns seems a bit stiff, such as Swift: Head, Wings, Ass. empty empty is an empty sequence that just sends a .Completed message. - example( "empty" ) {
- let emptySequence: Observable<Int> = empty()
-
- let subscription = emptySequence
- .subscribe { event in
- print(event)
- }
- }
-
- --- empty example ---
- Completed
never never is an empty sequence that has no elements and does not emit any events. - example( "never" ) {
- let neverSequence: Observable<String> = never()
-
- let subscription = neverSequence
- .subscribe { _ in
- print( "This block is never called." )
- }
- }
-
- --- never example ---
just just is a sequence that contains only one element. It will first send .Next(value) and then send .Completed . - example( "just" ) {
- let singleElementSequence = just( 32 )
-
- let subscription = singleElementSequence
- .subscribe { event in
- print(event)
- }
- }
-
- --- just example ---
- Next( 32 )
- Completed
sequenceOf sequenceOf can convert a series of elements into a sequence of events.
- example( "sequenceOf" ) {
- let sequenceOfElements = sequenceOf( 0 , 1 , 2 , 3 )
-
- let subscription = sequenceOfElements
- .subscribe { event in
- print(event)
- }
- }
-
- --- sequenceOf example ---
- Next( 0 )
- Next( 1 )
- Next( 2 )
- Next( 3 )
- Completed
form converts a sequence ( SequenceType ) in Swift into an event sequence through asObservable() method. - example( "from" ) {
- let sequenceFromArray = [ 1 , 2 , 3 , 4 , 5 ].asObservable()
-
- let subscription = sequenceFromArray
- .subscribe { event in
- print(event)
- }
- }
-
- --- from example ---
- Next( 1 )
- Next( 2 )
- Next( 3 )
- Next( 4 )
- Next( 5 )
- Completed
create create can create a sequence through a closure and add events through .on(e: Event) . - example( "create" ) {
- let myJust = { (singleElement: Int) -> Observable<Int> in
- return create { observer in
- observer.on(.Next(singleElement))
- observer.on(.Completed)
-
- return NopDisposable.instance
- }
- }
-
- let subscription = myJust( 5 )
- .subscribe { event in
- print(event)
- }
- }
-
- --- create example ---
- Next( 5 )
- Completed
failWith failWith creates a sequence with no elements and only emits failure ( .Error ) events. - example( "failWith" ) {
- let error = NSError(domain: "Test" , code: - 1 , userInfo: nil)
-
- let erroredSequence: Observable<Int> = failWith(error)
-
- let subscription = erroredSequence
- .subscribe { event in
- print(event)
- }
- }
-
- --- failWith example ---
- Error(Error Domain=Test Code=- 1 "The operation couldn't be completed. (Test error -1.)" )
deferred deferred will wait until there is a subscriber and then create an Observable object through the factory method. The object subscribed by each subscriber is a completely independent sequence with the same content. - example( "deferred" ) {
- let deferredSequence: Observable<Int> = deferred {
- print( "creating" )
- return create { observer in
- print( "emmiting" )
- observer.on(.Next( 0 ))
- observer.on(.Next( 1 ))
- observer.on(.Next( 2 ))
-
- return NopDisposable.instance
- }
- }
-
- print( "go" )
-
- deferredSequence
- .subscribe { event in
- print(event)
- }
-
- deferredSequence
- .subscribe { event in
- print(event)
- }
- }
-
- --- deferred example ---
- go
- creating
- emmiting
- Next( 0 )
- Next( 1 )
- Next( 2 )
- creating
- emmiting
- Next( 0 )
- Next( 1 )
- Next( 2 )
Why do we need a strange guy like defferd ? In fact, this is equivalent to delayed loading, because the data may not be fully loaded when adding a listener, such as the following example: - example( "TestDeferred" ) {
- var value: String? = nil
- var subscription: Observable<String?> = just(value)
-
-
- value = "Hello!"
-
- subscription.subscribe { event in
- print(event)
- }
- }
-
- --- TestDeferred example ---
- Next(nil)
- Completed
If deffered is used, the desired data can be displayed normally: - example( "TestDeferred" ) {
- var value: String? = nil
- var subscription: Observable<String?> = deferred {
- return just(value)
- }
-
-
- value = "Hello!"
-
- subscription.subscribe { event in
- print(event)
- }
-
- }
-
- --- TestDeferred example ---
- Next(Optional( "Hello!" ))
- Completed
#p# Subjects Next is the content about Subject . Subject can be seen as a proxy and a bridge. It is both a subscriber and a source, which means it can subscribe to other Observable objects and send events to its subscribers. If you think of Observable as a pipe that continuously outputs events, then Subject is the faucet on it. It not only holds a pipe that continuously outputs water, but also delivers fresh water to the outside. If you use a cup to directly collect water from the pipe, you may be unable to control the amount of aqua regia glue that comes out; if you collect water under the faucet, you can adjust the water speed and temperature as you like. (Okay, the above paragraph is not in the document, I just made it up, if I misunderstood, please slap me in the face ( ̄ε(# ̄)☆╰╮( ̄▽ ̄///)) Before starting the following code, define a helper function for outputting data: - func writeSequenceToConsole<O: ObservableType>(name: String, sequence: O) {
- sequence
- .subscribe { e in
- print( "Subscription: \(name), event: \(e)" )
- }
- }
PublishSubject PublishSubject will send the sequence of events to the subscriber since the subscription. - example( "PublishSubject" ) {
- let subject = PublishSubject<String>()
- writeSequenceToConsole( "1" , sequence: subject)
- subject.on(.Next( "a" ))
- subject.on(.Next( "b" ))
- writeSequenceToConsole( "2" , sequence: subject)
- subject.on(.Next( "c" ))
- subject.on(.Next( "d" ))
- }
-
-
- ---PublishSubject example ---
- Subscription: 1 , event: Next(a)
- Subscription: 1 , event: Next(b)
- Subscription: 1 , event: Next(c)
- Subscription: 2 , event: Next(c)
- Subscription: 1 , event: Next(d)
- Subscription: 2 , event: Next(d)
ReplaySubject ReplaySubject will resend all the data queues that have been sent when a new subscriber subscribes. bufferSize is the size of the buffer, which determines the maximum value of the resend queue. If bufferSize is 1, then when a new subscriber appears, it will resend the previous event. If it is 2, it will resend two, and so on. - example( "ReplaySubject" ) {
- let subject = ReplaySubject<String>.create(bufferSize: 1 )
-
- writeSequenceToConsole( "1" , sequence: subject)
- subject.on(.Next( "a" ))
- subject.on(.Next( "b" ))
- writeSequenceToConsole( "2" , sequence: subject)
- subject.on(.Next( "c" ))
- subject.on(.Next( "d" ))
- }
-
- --- ReplaySubject example ---
- Subscription: 1 , event: Next(a)
- Subscription: 1 , event: Next(b)
- Subscription: 2 , event: Next(b)
- Subscription: 1 , event: Next(c)
- Subscription: 2 , event: Next(c)
- Subscription: 1 , event: Next(d)
- Subscription: 2 , event: Next(d)
BehaviorSubject BehaviorSubject sends the most recently sent event to a new subscriber when it subscribes, or a default value if none. - example( "BehaviorSubject" ) {
- let subject = BehaviorSubject(value: "z" )
- writeSequenceToConsole( "1" , sequence: subject)
- subject.on(.Next( "a" ))
- subject.on(.Next( "b" ))
- writeSequenceToConsole( "2" , sequence: subject)
- subject.on(.Next( "c" ))
- subject.on(.Completed)
- }
-
- --- BehaviorSubject example ---
- Subscription: 1 , event: Next(z)
- Subscription: 1 , event: Next(a)
- Subscription: 1 , event: Next(b)
- Subscription: 2 , event: Next(b)
- Subscription: 1 , event: Next(c)
- Subscription: 2 , event: Next(c)
- Subscription: 1 , event: Completed
- Subscription: 2 , event: Completed
Variable Variable is a layer of encapsulation based on BehaviorSubject . Its advantage is that it will not be explicitly terminated. That is, it will not receive termination events such as .Completed and .Error . It will actively send .Complete when it is destroyed. e - xample( "Variable" ) {
- let variable = Variable( "z" )
- writeSequenceToConsole( "1" , sequence: variable)
- variable.value = "a"
- variable.value = "b
- writeSequenceToConsole( "2" , sequence: variable)
- variable.value = "c"
- }
-
- --- Variable example ---
- Subscription: 1 , event: Next(z)
- Subscription: 1 , event: Next(a)
- Subscription: 1 , event: Next(b)
- Subscription: 2 , event: Next(b)
- Subscription: 1 , event: Next(c)
- Subscription: 2 , event: Next(c)
- Subscription: 1 , event: Completed
- Subscription: 2 , event: Completed
We can perform some transformations on sequences, similar to the various transformations of CollectionType in Swift. This has been mentioned in previous posts, and you can refer to: Functional functions. map map is to use a function to transform each element and map them one by one. - example( "map" ) {
- let originalSequence = sequenceOf( 1 , 2 , 3 )
-
- originalSequence
- .map { $ 0 * 2 }
- .subscribe { print($ 0 ) }
- }
-
- --- map example ---
- Next( 2 )
- Next( 4 )
- Next( 6 )
- Completed
flatMap When performing a transformation, map is prone to "dimensionality increase", that is, after the transformation, it changes from a sequence to a sequence of sequences. What is "dimensionality increase"? In the set, we can give such an example. I have a list of friends [p1, p2, p3] . If I want to get the list of my friends' friends, I can do it like this: myFriends. map { $0 .getFriends() } The result is [[p1-1, p1-2, p1-3], [p2-1], [p3-1, p3-2]] , which is a list of friends' friends' lists. This is an example of "dimensionality increase". (The above content is still not in the document, it is still my own nonsense, and I still welcome you to slap me in the face if there are any mistakes ( ̄ε(# ̄)☆╰╮( ̄▽ ̄///)) In Swift, we can use flatMap to filter out nil results after map . In Rx, flatMap can convert a sequence into a group of sequences, and then "flatten" this group of sequences into a sequence. - example( "flatMap" ) {
- let sequenceInt = sequenceOf( 1 , 2 , 3 )
- let sequenceString = sequenceOf( "A" , "B" , "--" )
-
- sequenceInt
- .flatMap { int in
- sequenceString
- }
- .subscribe {
- print($ 0 )
- }
- }
-
- --- flatMap example ---
- Next(A)
- Next(B)
- Next(--)
- Next(A)
- Next(B)
- Next(--)
- Next(A)
- Next(B)
- Next(--)
- Completed
scan - Scan is a bit like reduce. It accumulates the results of each operation and uses them as the input value for the next operation.
-
- example( "scan" ) {
- let sequenceToSum = sequenceOf( 0 , 1 , 2 , 3 , 4 , 5 )
-
- sequenceToSum
- .scan( 0 ) { acum, elem in
- acum + elem
- }
- .subscribe {
- print($ 0 )
- }
- }
-
- --- scan example ---
- Next( 0 )
- Next( 1 )
- Next( 3 )
- Next( 6 )
- Next( 10 )
- Next( 15 )
- Completed
#p#
Filtering In addition to the above transformations, we can also filter sequences. filter filter will only let the elements that meet the conditions pass. - example( "filter" ) {
- let subscription = sequenceOf( 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 )
- .filter {
- $ 0 % 2 == 0
- }
- .subscribe {
- print($ 0 )
- }
- }
-
- --- filter example ---
- Next( 0 )
- Next( 2 )
- Next( 4 )
- Next( 6 )
- Next( 8 )
- Completed
distinctUntilChanged- distinctUntilChanged will discard duplicate events.
-
- example( "distinctUntilChanged" ) {
- let subscription = sequenceOf( 1 , 2 , 3 , 1 , 1 , 4 )
- .distinctUntilChanged()
- .subscribe {
- print($ 0 )
- }
- }
-
- --- distinctUntilChanged example ---
- Next( 1 )
- Next( 2 )
- Next( 3 )
- Next( 1 )
- Next( 4 )
- Completed
take take only gets the first n events in the sequence and will automatically .Completed after the number is met. - example( "take" ) {
- let subscription = sequenceOf( 1 , 2 , 3 , 4 , 5 , 6 )
- .take( 3 )
- .subscribe {
- print($ 0 )
- }
- }
-
- --- take example ---
- Next( 1 )
- Next( 2 )
- Next( 3 )
- Completed
Combining This part is about sequence operations, which can combine multiple sequence sources into a new event sequence. startWith- startWith inserts an event element before the beginning of the queue.
-
- example( "startWith" ) {
- let subscription = sequenceOf( 4 , 5 , 6 )
- .startWith( 3 )
- .subscribe {
- print($ 0 )
- }
- }
-
- --- startWith example ---
- Next( 3 )
- Next( 4 )
- Next( 5 )
- Next( 6 )
- Completed
combineLatest If there are two event queues that need to be listened to at the same time, then whenever a new event occurs, combineLatest will merge the last element of each queue.
- example( "combineLatest 1" ) {
- let intOb1 = PublishSubject<String>()
- let intOb2 = PublishSubject<Int>()
-
- combineLatest(intOb1, intOb2) {
- "\($0) \($1)"
- }
- .subscribe {
- print($ 0 )
- }
-
- intOb1.on(.Next( "A" ))
- intOb2.on(.Next( 1 ))
- intOb1.on(.Next( "B" ))
- intOb2.on(.Next( 2 ))
- }
-
- --- combineLatest 1 example ---
- Next(A 1 )
- Next(B 1 )
- Next(B 2 )
zip- As the name suggests, zip is used to compress two queues, but it will wait until the elements of the two queues are gathered one by one before merging them.
-
- example( "zip 1" ) {
- let intOb1 = PublishSubject<String>()
- let intOb2 = PublishSubject<Int>()
- zip(intOb1, intOb2) {
- "\($0) \($1)"
- }
- .subscribe {
- print($ 0 )
- }
- intOb1.on(.Next( "A" ))
- intOb2.on(.Next( 1 ))
- intOb1.on(.Next( "B" ))
- intOb1.on(.Next( "C" ))
- intOb2.on(.Next( 2 ))
- }
-
- --- zip 1 example ---
- Next(A 1 )
- Next(B 2 )
marge merge means merge, which combines two queues together in order. - example( "merge 1" ) {
- let subject1 = PublishSubject<Int>()
- let subject2 = PublishSubject<Int>()
-
- sequenceOf(subject1, subject2)
- .merge()
- .subscribeNext { int in
- print( int )
- }
-
- subject1.on(.Next( 1 ))
- subject1.on(.Next( 2 ))
- subject2.on(.Next( 3 ))
- subject1.on(.Next( 4 ))
- subject2.on(.Next( 5 ))
- }
-
- --- merge 1 example ---
- 1
- 2
- 3
- 4
- 5
#p# switch When your event sequence is a sequence of event sequences ( Observable<Observable<T>> ), you can use switch to flatten the sequence of sequences into one dimension, and automatically switch to the last sequence when a new sequence appears. Similar to merge , it also plays the role of "flattening" multiple sequences into one sequence. - example( "switchLatest" ) {
- let var1 = Variable( 0 )
-
- let var2 = Variable( 200 )
-
-
- let var3 = Variable(var1)
-
- let d = var3
- .switchLatest()
- .subscribe {
- print($ 0 )
- }
-
- var1.value = 1
- var1.value = 2
- var1.value = 3
- var1.value = 4
-
- var3.value = var2
- var2.value = 201
- var1.value = 5
-
- var3.value = var1
- var2.value = 202
- var1.value = 6
- }
-
- --- switchLatest example ---
- Next( 0 )
- Next( 1 )
- Next( 2 )
- Next( 3 )
- Next( 4 )
- Next( 200 )
- Next( 201 )
- Next( 5 )
- Next( 6 )
Note that although both are "flattening", they are different from flatmap . flatmap transforms one sequence into another sequence, and this transformation process will make the dimension higher, so it needs to be "flattened", while switch flattens the original two-dimensional sequence (sequence of sequences) into a one-dimensional sequence. Error Handling It is normal to encounter exceptions in a sequence of events. There are several ways to handle exceptions. catchError catchError can capture abnormal events and seamlessly connect another event sequence without any trace of the abnormality. - example( "catchError 1" ) {
- let sequenceThatFails = PublishSubject<Int>()
- let recoverySequence = sequenceOf( 100 , 200 )
-
- sequenceThatFails
- .catchError { error in
- return recoverySequence
- }
- .subscribe {
- print($ 0 )
- }
-
- sequenceThatFails.on(.Next( 1 ))
- sequenceThatFails.on(.Next( 2 ))
- sequenceThatFails.on(.Error(NSError(domain: "Test" , code: 0 , userInfo: nil)))
- }
-
- --- catchError 1 example ---
- Next( 1 )
- Next( 2 )
- Next( 100 )
- Next( 200 )
- Completed
retry As the name suggests, retry means that when an exception occurs, it will subscribe to the event sequence again, trying to solve the exception by "starting over again". - example( "retry" ) {
- var count = 1
- let funnyLookingSequence: Observable<Int> = create { observer in
- let error = NSError(domain: "Test" , code: 0 , userInfo: nil)
- observer.on(.Next( 0 ))
- observer.on(.Next( 1 ))
- if count < 2 {
- observer.on(.Error(error))
- count++
- }
- observer.on(.Next( 2 ))
- observer.on(.Completed)
-
- return NopDisposable.instance
- }
-
- funnyLookingSequence
- .retry()
- .subscribe {
- print($ 0 )
- }
- }
-
- --- retry example ---
- Next( 0 )
- Next( 1 )
- Next( 0 )
- Next( 1 )
- Next( 2 )
- Completed
Utility Here are some methods for event sequences. subscribe subscribe has been touched upon before, and it will be triggered when there is a new event. - example "subscribe") {
- let sequenceOfInts = PublishSubject < Int > ()
-
- sequenceOfInts
- .subscribe {
- print($0)
- }
-
- sequenceOfInts.on(.Next(1))
- sequenceOfInts.on(.Completed)
- }
-
- --- subscribe example ---
- Next(1)
- Completed
-
- subscribeNext
-
- subscribeNext is also a subscription, but only subscribes to the .Next event.
-
- example("subscribeNext") {
- let sequenceOfInts = PublishSubject < Int > ()
-
- sequenceOfInts
- .subscribeNext {
- print($0)
- }
-
- sequenceOfInts.on(.Next(1))
- sequenceOfInts.on(.Completed)
- }
-
- --- subscribeNext example ---
- 1
subscribeCompleted- subscribeCompleted only subscribes to the .Completed completion event.
-
- example( "subscribeCompleted" ) {
- let sequenceOfInts = PublishSubject<Int>()
-
- sequenceOfInts
- .subscribeCompleted {
- print( "It's completed" )
- }
-
- sequenceOfInts.on(.Next( 1 ))
- sequenceOfInts.on(.Completed)
- }
-
- --- subscribeCompleted example ---
- It's completed
subscribeError- subscribeError only subscribes to the .Error failure event.
-
- example( "subscribeError" ) {
- let sequenceOfInts = PublishSubject<Int>()
-
- sequenceOfInts
- .subscribeError { error in
- print(error)
- }
-
- sequenceOfInts.on(.Next( 1 ))
- sequenceOfInts.on(.Error(NSError(domain: "Examples" , code: - 1 , userInfo: nil)))
- }
-
- --- subscribeError example ---
- Error Domain=Examples Code=- 1 "The operation couldn't be completed. (Examples error -1.)"
#p#
doOn- doOn can listen for events and is called before the event occurs.
-
- example( "doOn" ) {
- let sequenceOfInts = PublishSubject<Int>()
-
- sequenceOfInts
- .doOn {
- print( "Intercepted event \($0)" )
- }
- .subscribe {
- print($ 0 )
- }
-
- sequenceOfInts.on(.Next( 1 ))
- sequenceOfInts.on(.Completed)
- }
-
- --- doOn example ---
- Intercepted event Next( 1 )
- Next( 1 )
- Intercepted event Completed
- Completed
Conditional We can make some complex logical judgments on multiple event sequences. takeUntil takeUntil is actually take , which will trigger the .Completed event after finally waiting for that event.
- example( "takeUntil" ) {
- let originalSequence = PublishSubject<Int>()
- let whenThisSendsNextWorldStops = PublishSubject<Int>()
-
- originalSequence
- .takeUntil(whenThisSendsNextWorldStops)
- .subscribe {
- print($ 0 )
- }
-
- originalSequence.on(.Next( 1 ))
- originalSequence.on(.Next( 2 ))
-
- whenThisSendsNextWorldStops.on(.Next( 1 ))
-
- originalSequence.on(.Next( 3 ))
- }
-
- --- takeUntil example ---
- Next( 1 )
- Next( 2 )
- Completed
takeWhile takeWhile can determine whether to continue take through the status statement.
- example( "takeWhile" ) {
- let sequence = PublishSubject<Int>()
- sequence
- .takeWhile { int in
- int < 2
- }
- .subscribe {
- print($ 0 )
- }
- sequence.on(.Next( 1 ))
- sequence.on(.Next( 2 ))
- sequence.on(.Next( 3 ))
- }
-
- --- takeWhile example ---
- Next( 1 )
- Completed
Aggregate We can perform some set operations on event sequences. concat concat can combine multiple event sequences.
- example( "concat" ) {
- let var1 = BehaviorSubject(value: 0 )
- let var2 = BehaviorSubject(value: 200 )
-
-
- let var3 = BehaviorSubject(value: var1)
-
- let d = var3
- .concat()
- .subscribe {
- print($ 0 )
- }
-
- var1.on(.Next( 1 ))
- var1.on(.Next( 2 ))
-
- var3.on(.Next(var2))
-
- var2.on(.Next( 201 ))
-
- var1.on(.Next( 3 ))
- var1.on(.Completed)
-
- var2.on(.Next( 202 ))
- }
-
- --- concat example ---
- Next( 0 )
- Next( 1 )
- Next( 2 )
- Next( 3 )
- Next( 201 )
- Next( 202 )
reduce reduce here has the same meaning as reduce in CollectionType , both of which refer to generating a result by operating on a series of data. - example( "reduce" ) {
- sequenceOf( 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 )
- .reduce( 0 , +)
- .subscribe {
- print($ 0 )
- }
- }
-
- --- reduce example ---
- Next( 45 )
- Completed
Next That’s about it for the basic introduction. With the preparation of "Functional Reactive Programming in Swift - Part 1", it seems very pleasant to understand. However, it is still not deep enough. In the next chapter, we will practice it in a specific project. Let's practice! Run to the playground, boy! Run the playground in your Xcode!
References: - ReactiveX
- RAC Marbles
- defer
- jQuery's deferred object explained
- jQuery Deferred object promise method
- Deferring Observable code until subscription in RxJava
- To Use Subject Or Not To Use Subject?
- flatmap
- Grokking RxJava, Part 2: Operator, Operator
- Transformation - Select Many
- RxJava Observable transformation: concatMap() vs flatMap()
- Recursive Observables with RxJava
- Combining sequences
- switch
- RxSwift Getting Started
|