Development record of developer who study hard everyday.

, ,

How to subscribe one publisher simultaneously in Swift Combine

 How to subscribe one publisher simultaneously in Swift Combine


1. Limitation of Publishers when several subscribers exists

import UIKit

import Combine


var cancellables = Set<AnyCancellable>()


//This is Publishers.Zip which combine two publishers

func valuePublisher() -> Publishers.Zip<Publishers.Sequence<[Int], Never>, Publishers.Autoconnect<Timer.TimerPublisher>> {

    

    let timerPublisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()

    

    return Publishers.Zip([1, 2, 3, 4, 5, 6, 7].publisher, timerPublisher)

}


func simpleMulticast() {

    let zipPublisher = valuePublisher()

    

    let publisher = zipPublisher

        .print()

        .map { "\($0.0)" }

        .eraseToAnyPublisher()

        

    publisher

        .map{ "subscribtion1 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    publisher

        .map{ "subscribtion2 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    //subscribe after 3 seconds

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {

        publisher

            .map { "subscribtion3 - \($0)" }

            .sink { print($0) }

            .store(in: &cancellables)

    }

}


simpleMulticast()


The result of above code is

receive subscription: (Zip)

request unlimited

receive subscription: (Zip)

request unlimited

receive value: ((1, 2025-01-05 13:24:30 +0000))

subscribtion2 - 1

receive value: ((1, 2025-01-05 13:24:30 +0000))

subscribtion1 - 1

receive value: ((2, 2025-01-05 13:24:31 +0000))

subscribtion2 - 2

receive value: ((2, 2025-01-05 13:24:31 +0000))

subscribtion1 - 2

receive value: ((3, 2025-01-05 13:24:32 +0000))

subscribtion2 - 3

receive value: ((3, 2025-01-05 13:24:32 +0000))

subscribtion1 - 3

receive subscription: (Zip)

request unlimited

receive value: ((4, 2025-01-05 13:24:33 +0000))

subscribtion2 - 4

receive value: ((4, 2025-01-05 13:24:33 +0000))

subscribtion1 - 4

receive value: ((1, 2025-01-05 13:24:33 +0000))

subscribtion3 - 1

receive value: ((5, 2025-01-05 13:24:34 +0000))

subscribtion2 - 5

receive value: ((5, 2025-01-05 13:24:34 +0000))

subscribtion1 - 5

receive value: ((2, 2025-01-05 13:24:34 +0000))

subscribtion3 - 2

receive value: ((6, 2025-01-05 13:24:35 +0000))

subscribtion2 - 6

receive value: ((6, 2025-01-05 13:24:35 +0000))

subscribtion1 - 6

receive value: ((3, 2025-01-05 13:24:35 +0000))

subscribtion3 - 3

receive value: ((7, 2025-01-05 13:24:36 +0000))

subscribtion2 - 7

receive finished

receive value: ((7, 2025-01-05 13:24:36 +0000))

subscribtion1 - 7

receive finished

receive value: ((4, 2025-01-05 13:24:36 +0000))

subscribtion3 - 4

receive value: ((5, 2025-01-05 13:24:37 +0000))

subscribtion3 - 5

receive value: ((6, 2025-01-05 13:24:38 +0000))

subscribtion3 - 6

receive value: ((7, 2025-01-05 13:24:39 +0000))

subscribtion3 - 7

receive finished

In this result, same log is printed repeatedly whenever one subscriber receives value.

Moreover, third subscriber receives not recent value but oldest value.

This is quiet strange and inefficient.

To get over this limitation, we use multicast or share method.


2. Example of multicast method

import UIKit

import Combine


var cancellables = Set<AnyCancellable>()


//This is Publishers.Zip which combine two publishers

func valuePublisher() -> Publishers.Zip<Publishers.Sequence<[Int], Never>, Publishers.Autoconnect<Timer.TimerPublisher>> {

    

    let timerPublisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()

    

    return Publishers.Zip([1, 2, 3, 4, 5, 6, 7].publisher, timerPublisher)

}



let subject = PassthroughSubject<String, Never>()


func multicast() {

    print("multicast called")

    let zipPublisher = valuePublisher()

    

    let publisher = zipPublisher

        .print()

        .map { "\($0.0)" }

        .eraseToAnyPublisher()

    
    //multicast zipPublisher using PassthroughSubject

    let multicast = publisher.multicast(subject: subject)

    

    multicast

        .map{ "subscribtion1 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    multicast

        .map{ "subscribtion2 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    
    //Subscribing starts only when Publishers.Multicast is connected

    multicast.connect().store(in: &cancellables)

    

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {

        multicast

            .map { "subscribtion3 - \($0)" }

            .sink { print($0) }

            .store(in: &cancellables)

    }

}


multicast()


The result of multicast is

multicast called

receive subscription: (Zip)

request unlimited

receive value: ((1, 2025-01-05 13:34:45 +0000))

subscribtion2 - 1

subscribtion1 - 1

receive value: ((2, 2025-01-05 13:34:46 +0000))

subscribtion2 - 2

subscribtion1 - 2

receive value: ((3, 2025-01-05 13:34:47 +0000))

subscribtion2 - 3

subscribtion1 - 3

receive value: ((4, 2025-01-05 13:34:48 +0000))

subscribtion2 - 4

subscribtion1 - 4

subscribtion3 - 4

receive value: ((5, 2025-01-05 13:34:49 +0000))

subscribtion2 - 5

subscribtion1 - 5

subscribtion3 - 5

receive value: ((6, 2025-01-05 13:34:50 +0000))

subscribtion2 - 6

subscribtion1 - 6

subscribtion3 - 6

receive value: ((7, 2025-01-05 13:34:51 +0000))

subscribtion2 - 7

subscribtion1 - 7

subscribtion3 - 7

receive finished

This is the real multi subscribing!

The third subscriber receives recent value and There are not repeated log.


3. Example of Share method

import UIKit

import Combine


var cancellables = Set<AnyCancellable>()


//This is Publishers.Zip which combine two publishers

func valuePublisher() -> Publishers.Zip<Publishers.Sequence<[Int], Never>, Publishers.Autoconnect<Timer.TimerPublisher>> {

    

    let timerPublisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()

    

    return Publishers.Zip([1, 2, 3, 4, 5, 6, 7].publisher, timerPublisher)

}


func shareMulticast() {

    let zipPublisher = valuePublisher()

    

    let publisher = zipPublisher

        .print()

        .map { "\($0.0)" }

        .eraseToAnyPublisher()

    

    let multicastSubject = publisher.share()

    

    multicastSubject

        .map{ "subscribtion1 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    multicastSubject

        .map{ "subscribtion2 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

        

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {

        multicastSubject

            .map { "subscribtion3 - \($0)" }

            .sink { print($0) }

            .store(in: &cancellables)

    }}


shareMulticast()


The result of share method is

multicast called

receive subscription: (Zip)

request unlimited

receive value: ((1, 2025-01-05 13:34:45 +0000))

subscribtion2 - 1

subscribtion1 - 1

receive value: ((2, 2025-01-05 13:34:46 +0000))

subscribtion2 - 2

subscribtion1 - 2

receive value: ((3, 2025-01-05 13:34:47 +0000))

subscribtion2 - 3

subscribtion1 - 3

receive value: ((4, 2025-01-05 13:34:48 +0000))

subscribtion2 - 4

subscribtion1 - 4

subscribtion3 - 4

receive value: ((5, 2025-01-05 13:34:49 +0000))

subscribtion2 - 5

subscribtion1 - 5

subscribtion3 - 5

receive value: ((6, 2025-01-05 13:34:50 +0000))

subscribtion2 - 6

subscribtion1 - 6

subscribtion3 - 6

receive value: ((7, 2025-01-05 13:34:51 +0000))

subscribtion2 - 7

subscribtion1 - 7

subscribtion3 - 7

receive finished

The result is same as multicast method example.

However, you don't need to connect Publisher.Share.

This is strength of share method.

Share:
Location: 미국 뉴욕

댓글 없음:

댓글 쓰기