Skip to content

Instantly share code, notes, and snippets.

@lintmachine
Last active December 14, 2022 02:43
Show Gist options
  • Save lintmachine/d5dc1b48d88b445329b716469f482785 to your computer and use it in GitHub Desktop.
Save lintmachine/d5dc1b48d88b445329b716469f482785 to your computer and use it in GitHub Desktop.
Combine Publisher / Subscriber Handshake

Combine Publisher / Subscriber Handshake

The Publisher receives the Subscriber

      Publisher.receive(subscriber:)

The Publisher creates the Subscription passing in self (the Publisher) and the Subscriber

      let subscription = Subscription(self, subscriber)

Note: The Subscription retains a strong reference to the Publisher and a weak reference to the Subscriber

  • The Subscription is "owned" by the Subscriber, so we keep a weak reference to avoid a retain cycle.
  • Each side has a method of ending the Subscription.
    • The Subscription adopts Cancellable, which allows the Subscriber to cancel the Subscription directly.
      • The Subscriber can call subscription.cancel() directly
      • Most often the Subscription is wrapped in an AnyCancellable, which will automatically call .cancel() when it is de-initialized.
        • If the Subscriber is the only object holding a reference to the AnyCancellable, which is most often the case... then the Subscription will be canceled when the Subscriber is de-initialized, tying the lifetime of the Subscriber and the Subscription together.
    • The Publisher can cancel the Subscription by sending a .completion event to the Subscriber.

The Subscriber receives the Subscription

      subscriber.receive(subscription)

and requests a demand for some number of values (often the demand is .unlimited)

      subscription.request(.unlimited)

Note: The Subscriber is only allowed to call subscription.request(_ demand:) a single time. This may seem surprising, but I think it makes sense if you understand the next part.

The Subscription receives the requested .demand.

  • The Subscription starts the Publisher producing some value(s)
  • Hopefully producing asynchronously, since that's kind of the point, but it's not strictly necessary...

The Publisher produces a new value which is sent to the Subscriber.

      let newDemand = subscriber.receive(state)

The Subscriber receives the published value and returns an additional .demand to the Subscription.

  • optionally, of course.

Note: This is the mechanism for requesting additional values and managing back-pressure from the Subscriber.

  • As I understand it, calling .request on a Subscription more than once is a violation of the contract, and should be considered a programming error.
  • Which I think makes sense

Consider a Subscription as a one-way iterator for the Publisher's contents.

  • We can't re-use an iterator once it's been advanced to the end.
  • Instead we have to create a new iterator from the Collection.
  • Once a Subscription is started, when a demand request has been made, it's a one-way trip.
  • We have to request a new Subscription from the Publisher if we want to go again. 🎡
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment