Asynchronous Events Handling Cheatsheet
Previously RxSwift/RxJava are commonly recognized as the industry standard regarding to reactive programming. But in recent year, both Android and iOS officially promoted their own aynchrinous event library, namely coroutine and combine. Since all those libraries are handling asynchronous events and following the same patterns, it will be very easy to confuse with the details with the same terminology on different frameworks.
So I am trying to workout a cheatsheet to compare the similarities and differences between these frameworks. [WIP, I am still polishing on this document to add coroutine in and add more references and examples].
1. Comparison:
The terms used across RxSwift/RxJava/Combine/Coroutine are summarized into the table below:
Terms | Description | RxSwift | RxJava | Combine | Coroutine |
---|---|---|---|---|---|
Observer | Event recievers which receives the signals emmited from observable, can adjust the dispatch thread via observeOn(affecting downstream)/subscribeOn(affecting upstream) | (x) | (x) | ||
Observable | Sources can be observed, can be implemented by oneself or created using operators or convenience constructors like Observable.create() . If you create a observable like this, it is cold, but later can be converted to hot via connectable , subscribing to a observable returns a Disposable . Dispose a disposable will terminate the event flow. |
(x) | (x) | ||
Subscriber | Usually implements the disposable interfaces, returned via subscribing an observable object (in Rx) or work with publisher (in combine) | (x) works with observable | (x) works with observable | (x) works with publisher | |
Subscription | In combine, works with subscriber so can pull events. In Rx java, there are still protocol definitions for subscription. | (x) works with publisher/subscriber protocol | (x) works with subscriber | ||
Subject | A unique concept in Rx world, acts as both observable as well as observer, similarly to publisher in combine, but the difference is that subject can subscribe to multiple observers | (x) | (x) | ||
Disposable | A concept which is uniquely owned by RxSwift/RxJava | (x) | (x) | ||
Connectable | A concept describes type of observable can be connected, once connected, it starts to emit events. Such object is called hot observable | (x) works with observable/subject | (x) works with observable/subject | (x) works with Publisher to create a ConnectablePublisher | |
Publisher | Similar to subject, but mainly used in Combine, in Rx it is a empty protocol | (x) as protocol, not offering direct class/utility methods | (x) | ||
Flowable | Special concept in RxJava, a unique hot observable type which extends publisher protocols. Supporting buffers and other stream feature functions. | (x) | |||
BackPressure | Special concept in RxJava, used for handling intensive amount of event under hot observable dispatch. Providing strategies to deal with buffer full condition. | (x) | In Combine, there are also possible conditions causing backpressure. refer to this | ||
Operators | Decorators for observables/publishers, providing extra funtional programming paradigm related capabilities like thread management/creation/error management/filtering etc. | (x) | (x) | (x) | (x) |
Schedulers | Coordinates the places on which the operators and observable are executed. Usually worked together with observeOn and subscribeOn | (x) | (x) | (x) | (x) named as Dispatchers (but exactly the same thing) |
Blocking | Transforms the asynchronous event queue into a synchronous call (similar to a Future in Java) | (x) but is strongly discouraged to use, refers to here | (x) | not directly available in Combine, but the publisher.values property can produce a async sequence works together with swift concurrency |
|
Channel | Communication tunnel between two coroutines, which is very similar to a BlockingQueue (pull based), paired usage with .send() and .receive() , which also forms a cold pipeline. Using Fan-in /Fan-out , to recieve from or distribute to multiple channels. (In fan-out case, the events are evenly distributed accross recievers.) |
(x) | |||
Async Flow | Used to present a sequence of values which are asychronous computed. Which is actually pull based also, but you cannot control, by default the collect behaviour is a synchronous fetch process, backpressure also exists in this case, can use buffer to conflat or directly processing the latest event |
(x) |
Some other key points:
- Even though only RxJava currently supports
Flowable
&Backpressure
, but it should be a common challenge faced by all those frameworks. Without using backpressure operator, one can currently using sampling/throttling/debounce to mitigate the issue. - Cold observables are pull based, and hot observables are usually push based.
- Coroutine is not just aynchronous event handling, it also covers concurrency. (like swift combine + concurrency).
1.1 Swift Combine
1.2 RxSwift
RxSwift is verify similar to the concepts defined in Swift Combine, there is actually an official article compares the difference between the two. Mainly, Combine supports backpressure, and uses typed errors, but lacks debugging frameworks and UI layer frameworks(hmm, I can’t agree on this point, because it seems collaborates with async and SwiftUI well).
2. Functional Programming
When we are talking about reactive programming, we usually means FRP(functional reactive programming), where functions acts as operators to process streams of data in atomic granularity:
they are cascaded and composited to serve the need under different use cases. In deed, all the operators in the modern asynchronous frameworks we discussed so far can be called as Continuation Monad
.
The concepts of monads origins from Haskell, there are layers of concepts to explain in order to describe what is a Continuation Monad
.
In daily coding, sometimes we will need to wrap our value together with some context (a block of code pending for execution). This kind of structure is called a box. In Swift, for example, Optional is a box.
Functor: a object can unwrap the value in a Box and take a function as input, process the value with the function and put the output back into a new box.
Applicative Functor: a subtype of the functor, which can unwrap the value in a Box and a function in another box, execute the calculation and return a new box which contains the calculated result.
Let’s use the following code as an example:
extension Optional {
func apply<U>(f: (T -> U)?) -> U? {
switch f {
case .Some(let someF): return self.map(someF)
case .None: return .None
}
}
}
extension Array {
func apply<U>(fs: [Element -> U]) -> [U] {
var result = [U]()
for f in fs {
for element in self.map(f) {
result.append(element)
}
}
return result
}
}
infix operator <*> { associativity left }
func <*><T, U>(f: (T -> U)?, a: T?) -> U? {
return a.apply(f)
}
func <*><T, U>(f: [T -> U], a: [T]) -> [U] {
return a.apply(f)
}
Monad: Yes, it is a further subtype of applicative functor, which unwraps the internal value of a box and passed in a function which consumes the data and also returns another monad. (So the difference here between functor and monad is that monad itself does not decide how to return the new box value!)
infix operator -> { associativity left }
func -><T, U>(a: T?, f: T -> U?) -> U? {
return a.flatMap(f)
}
func half(a: Int) -> Int? {
return a % 2 == 0 ? a / 2 : .None
}
Optional(4) -> half
Continuation Monad: The partial application of monad, so the invocation can be cuscaded ~
postfix operator -->>
postfix func -->><T, U>(a: T?) -> (((T) -> U?) -> U?) {
return { action in
switch a {
case .none:
return .none
case .some(let some):
return action(some)
}
}
}
Now the code can be writen as below, which is just the common style of those operators in the reactive programming frameworks.
Optional(4)-->> { $0 + 1 }-->> { $0 * 9}-->> { $0 * 2}