We have been through six major iterations of ReactiveSwift since the beginning of ReactiveCocoa 3.0. It is perhaps the time for us to take a step back and review our fudemental hot-observable-centric model that offers an explicit distinction between hot and cold observable.
Pragmatically speaking, an API consumer given an observable never really cares about anything other than being pushed with events once subscribed.
We currently offer two primitives which are distinct from each other in two areas:
- Having triggered a side effect upon subscription or not; and
- Whether an observable multicasts its events or not.
From the said API consumer perspective, both these areas are unfortunately NOT an attribute of an observable itself. They have always been the semantics of the API vending the observable, since (1) an API consumer has neither late effect on these semantics, nor (2) has it ever mattered in the whole lifecycle of observation as a consumer. A consumer just expects events, and there is nothing more than that about an observable.
For example, let's consider this piece of API as a consumer:
public protocol ChatService {
func listAll() -> S<[Conversation], ChatError>
func fetchMessages(for conversation: Conversation.ID) -> S<[Message], ChatError>
}
Does it really matter to you as an API consumer what S
is in particular? As long as it vends the events as the ChatService
API promised as part of its interface, whether it is cold or hot does not affect the consumption of the observable.
Even if you know that S
has a particular semantic, in most cases one has aboslutely zero control over the behavior, since it is all up to however the owner/vendor wishes its observables to behave. The "exception" is only when the owner/vendor has extra API surface that could affect these observables, but then it is beyond the concern of any observable primitive, as it is the semantic of the API who just happens to use these primitives to deliver the results.
So from this point of view, we have been effectively encouraging our users to leak their API implementation details to their API consumers, despite having offered no value to any of the API consumer.
Where the split matters is when one looks from a producer perspective — it offers an explicit documentation value in whether a particular observable your API vended is hot or cold.
This is however the only advantage it has. It is weighed against a variety of issues, including but not limited to:
-
Twice the API surface
Users are provided with two sets of almost-identical API given the split.
-
Interoperability
While we have been improving in this area over time, interoperating between entities has not been easy, especially in light of the limitations of the Swift generics model.
-
Conceptual confusion
With (1) and (2), the distinction has been a source of confusion for beginners to grasp.
-
Implementation details
As mentioned in the previous section, these observable semantics are implementation details of the API from an API consumer perspective.
The on-going maintenance cost of an API surface twice large is growing. For example, to improve the interoperability experience without disruption in contextual lookup, we have to introduce a more specific overload to every generic operator.
The explanation actually works both ways:
-
Hot observables are the base.
Cold observables build a "sandboxed" graph of hot observables for every subscription, and execute a starting side effect afterwards.
-
Cold observables are the base.
A hot observable is just a cold observable connecting you with an event source as its starting side effect.
We just happened to choose the first explanation, motivated by its documentation value for API producers, and build the entire framework around it.
We all love ReactiveSwift for its simplicity as compared to competing frameworks. But even the simple ReactiveSwift itself still carries baggages that have been recognized as a source of confusion, and might have been less sensible as we iterate, learn and recalibrate our direction.
For us to move forward with a better development experience, I considered these being two important criteria:
- Does a split in this form offer us any value in spite of all downsides it has caused?
- Is it justifiable for the framework to make a drastic disruptive turn after six major iterations?
?
wow this is radical
.
.
.
a good one! Needs time to digest.