Search code examples
swiftcombinereactivex

When should we use Combine's multicast autoconnect()


I have this code

import Foundation
import Combine

let upstreamPublisher = Just("Hello, World!")
let sharedSubject = CurrentValueSubject<String, Never>("initial value")

// Create a multicast publisher and apply autoconnect
let multicastPublisher = upstreamPublisher
    .multicast(subject: sharedSubject)
    .autoconnect()

// Subscriber 1
multicastPublisher
    .sink { value in
        print("Subscriber 1 received value: \(value)")
    }

// Subscriber 2
multicastPublisher
    .sink { value in
        print("Subscriber 2 received value: \(value)")
    }

The output is:

Subscriber 1 received value: initial value
Subscriber 1 received value: Hello, World!

I read from an article that The first time you subscribe to it, it connects to the upstream publisher and starts the work immediately. This is useful in scenarios where the upstream publisher emits a single value and you can use a CurrentValueSubject to share it with subscribers.. The first part makes sense to me, but I don't know what the second part of the sentence really means. How can we share the results with current value subject and autoconnect?


Solution

  • How can we share the results with current value subject and autoconnect?

    This is already what your code is doing!

    I assume you expected subscriber 2 to also receive the initial value and "Hello, World!", i.e. an output like this

    Subscriber 1 received value: initial value
    Subscriber 2 received value: initial value
    Subscriber 2 received value: Hello, World!
    Subscriber 1 received value: Hello, World!
    

    This doesn't happen because Just publishes synchronously. As soon as you call sink, the subscriber is automatically connected to multicast (because autoconnect). As a result, Just publishes its value and completes immediately. When you add the second subscriber, everything is already completed and it doesn't receive anything.

    You can see this happening by adding .print("Subscriber 2") before sink. The output is:

    Subscriber 2: receive subscription: (Multicast)
    Subscriber 2: request unlimited
    Subscriber 2: receive finished
    

    If you turn the upstream into a publisher that doesn't publish values asynchronously, e.g. a network request, timer, delay, etc, then both subscribers get values:

    let upstreamPublisher = Just("Hello, World!")
        .delay(for: 1, scheduler: DispatchQueue.main)
    

    Make sure you store references to the AnyCancellables somewhere or the publisher will get cancelled before "Hello, World!" is published!


    Of course, there is always the option of calling connect manually.