heckj / swiftui-notes

content for Using Combine - notes on learning Combine with UIKit and SwiftUI

Home Page:https://heckj.github.io/swiftui-notes/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

"Operator is an object that acts both like a subscriber and a publisher" - Is this true?

rayx opened this issue · comments

The following text in Core Concepts -> Publisher and Subscriber section confuses me:

The third core concept is an operator - an object that acts both like a subscriber and a publisher. Operators are classes that adopt both the Subscriber protocol and Publisher protocol. They support subscribing to a publisher, and sending results to any subscribers.

Is it a copy-paste typo? I think operators are methods that operate on an Observable and return an Observable. See RX ofiical doc here.

I think the above text seems to describe subject (though subject in combine doesn't implement publisher protocol).

Hi @rayx -

It's more technically correct to say that an operator (which isn't defined by a protocol, it's more of a convention or concept) conforms to both the Publisher and Subscriber protocols, primarily implying that it has full control over receiving demand, processing it, propagating it upwards to get additional content, and returning relevant values in a fairly time efficient manner.

A subject supports both the Subject Protocol and the Publisher Protocol, but not the subscriber Protocol - which primarily ends up meaning it doesn't connect to upstream sources, and you need typically need (or want) to create intervening code and imperative functions to propagate any values with a subject. But that's entirely the intention of Subject in Combine - it's the primarily conceptual interface to imperative/non-Combine style code to allow data to flow into a Combine pipeline.

Observable isn't a protocol definition in Combine. As a conceptual layer, it's roughly equivalent to the combination of Publisher and Subscriber, but it's not a 100% clean semantic match. The Apple developers added some specific concepts that Observables didn't start with (the concept of back-pressure and the rules around how they handle it and how it propagates - or doesn't propagate - data flow), so I avoided trying to describe anything in Combine by Observable - it has quite a bit of explanatory baggage (and neat stuff too) - but since it doesn't align cleanly, trying to think of Combine with it can be tricky and confusing - as you're experiencing.

If you're starting from Rx or RxSwift, then you might find the detail that's been pulled together at https://github.com/CombineCommunity/rxswift-to-combine-cheatsheet useful - it's a mapping of the two projects and how/where they conceptually overlap.

It's more technically correct to say that an operator (which isn't defined by a protocol, it's more of a convention or concept) conforms to both the Publisher and Subscriber protocols, primarily implying that it has full control over receiving demand, processing it, propagating it upwards to get additional content, and returning relevant values in a fairly time efficient manner.

Hi @heckj, thanks for the detailed explanation. It has been very helpful. I see your point now and I agree it makes sense. Operators do work like proxy (or Python WSGI middleware). This is actually mentioned in Rx doc (I failed to notice it when I asked the question).

A Subject is a sort of bridge or proxy that is available in some 
implementations of ReactiveX that acts both as an observer and as an
Observable.

That said, I think the following is probably not true and perhaps should be removed? I'll leave it up to you to decide.

Operators are classes that adopt both the Subscriber protocol and 
Publisher protocol. 

Observable isn't a protocol definition in Combine. As a conceptual layer, it's roughly equivalent to the combination of Publisher and Subscriber, but it's not a 100% clean semantic match. The Apple developers added some specific concepts that Observables didn't start with (the concept of back-pressure and the rules around how they handle it and how it propagates - or doesn't propagate - data flow), so I avoided trying to describe anything in Combine by Observable - it has quite a bit of explanatory baggage (and neat stuff too) - but since it doesn't align cleanly, trying to think of Combine with it can be tricky and confusing - as you're experiencing.

Yes, I'm aware of this. I'm new to reactive programming. I started with reading some docs and videos about ReactiveX to understand its basic concepts. One thing that confused me was that "reactive" in ReactiveX vs the "reactive" in Akka. From my very limited understanding, ReactiveX was designed to address asynchronous programming issue, but Akka was designed to address distributed computing issue and introduced other concepts (including back pressure) to reactive programming.

A subject supports both the Subject Protocol and the Publisher Protocol, but not the subscriber Protocol - which primarily ends up meaning it doesn't connect to upstream sources, and you need typically need (or want) to create intervening code and imperative functions to propagate any values with a subject. But that's entirely the intention of Subject in Combine - it's the primarily conceptual interface to imperative/non-Combine style code to allow data to flow into a Combine pipeline.

If you're starting from Rx or RxSwift, then you might find the detail that's been pulled together at https://github.com/CombineCommunity/rxswift-to-combine-cheatsheet useful - it's a mapping of the two projects and how/where they conceptually overlap.

Thanks for the pointer. I didn't use any before. Your explanation have been very helpful. Feel free to close the issue if you'd like. Thanks!

For people who have the same confusion, I just realized a way to understand it.

Take subscriber as an example, it's an object in concept, But in swift the API to create a subscriber instance, say sink, is not to instantiate the sink class directly (as far as I can find, there isn't a public API for this subscriber class) but to call Publisher.sink() method instead. The sink() method returns a sink subscribe class, but that is not visible in the code due to type erasing.

The point is that the concept and the code (API or implementation details) don't necessarily align well.

In operator case, I suspect what happens is that Combine might define Operator class internally and, as the author mentioned, it conforms to Publisher and Subscriber protocols. This is implementation details so it's not exposed in API. In API level we use operator method to implicitly create and return these Operator objects. The caller doesn't see the Operator's type but just a publisher (because it conforms to Publisher protocol).

So, depending how we view it (from concept level vs implementation level), it's correct to think operators either as functions that transform a publisher to another publisher or as objects that conform to both Publisher and Subscriber protocols.