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.