Okay I see your concern of scalability. We need to limit size buffer, and I agree on that.
And I got a few more questions. I hope you to help me on my questions.
Does this mean even run-loops and dispatch-queues won't buffer any message (or functions) with PassthroughSubject.receive(on:)
? AFAIK, run-loops and dispatch-queues are designed to buffer any incoming messages implicitly and this makes me confused. If so, it would make sense to require attaching .buffer(...)
manually. Though I don't know how it's possible but maybe there's some secret... Does this mean Combine is designed to avoid even such implicit buffering? Like stopping upstream until target execution queue finishes processing last message?
Here's how I feel.
// I attach downstream in caller (current) context.
// `msg1` must be printed as downstream is attached here
// before sending it.
subject1.receive(on: gcdq1).sink({ print($0) })
subject1.send(msg1)
// I attach downstream in target (gcdq1) context.
// `msg1` must be ignored
// as the downstream is not attached before sending it.
gcdq1.async { subject1.receive(on: gcdq1).sink({ print($0) }) }
subject1.send(msg1)
IMO, Combine is showing second behavior for first expression.
What I don't understand is, having .receive(on:)
expression "looks" like I "attached" downstream "synchronously", but it actually isn't. Actually the receive(on:)
call itself happens in caller context synchronously, therefore I think my expectation makes sense. I'm not calling receive(on:)
"in" target execution context.
AFAIK, the main reason is sending "subscription start" message gets sent to downstream later. And AFAIK, this is because Combine is using "subscription start" as "attachment" signal.
But here in this case, I see this doesn't work. IMO, "attachment" is not same with "subscription". Especially in asynchronous pipeline. Attachment is done in caller context when I call receive(on:)
to build the pipeline, and receiving of such subscription start/value/end messages happens in other execution context asynchronously. At the point of attachment, "subscription start" has to be sent to target context, and consequent "sent value" has to be sent "after" the "subscription start" message. To me current situation looks like Combine is not sending "subscription start" message to target context because it believes "attachment" is not done at the point of calling receive(on:)
.
Though I don't know well about details of Combine, this doesn't seem to have any conflict with "no buffering" policy to me. No actual "sent message" need to be buffered to behave like this as "subscription start" is not a "sent messages" going through the pipeline.
This is just my opinion. I like to hear dev team opinion and direction, so I can direct future development.
This sounds like you are considering only UI use cases. I didn't say I am using this for UI. I am now using Combine as in-memory inter-component message passing channels. These components control subprocesses, call server API and aggregate data, but not really about UI. (Though final result goes to UI) As point of having this pipeline is "sending non-idempotent control/command/notification messages" rather than "representing state values", they should process everything precisely. They require absolute no message loss, duplication or shuffling on dynamic attach/detach and immediate message sending scenario. They should pass messages if attached, and should not if unattached. Only PassthroughSubject
matches for this purpose, not CurrentValueSubject
or ReplaySubject
as I think they potentially can send unexpected messages.
Should I consider Combine wouldn't match well for this purpose? @Jon_Shier @Tony_Parker