I have been playing with Combine to understand how it works in more details and I create a custom Publisher, Subscription and Subscriber.
Here's how it looks..
The emoji beamer publisher along with subscription:
struct EmojiBeamerPublisher: Publisher {
typealias Output = String
typealias Failure = Error
private let emojis: [String] = ["","❤️","✅","","","","","","","","❄️","","","","","✍️","","","","","","","","",""]
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
let subscription = EmojiBeamerSubscription(output: emojis, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
extension EmojiBeamerPublisher {
private final class EmojiBeamerSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Failure {
var subscriber: S?
let output: [String]
init(output: [String], subscriber: S) {
self.subscriber = subscriber
self.output = output
}
func request(_ demand: Subscribers.Demand) {
Swift.print("Demand: \(demand)") // Here I receive Unlimited demand
var demand = demand
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] timer in
guard let self = self else { return }
guard demand > 0, let subscriber = self.subscriber else {
timer.invalidate()
self.subscriber?.receive(completion: .finished)
self.cancel()
return
}
demand -= 1
demand += subscriber.receive(self.output.randomElement()! + " \(Date())")
}
}
func cancel() {
subscriber = nil
}
}
}
Here is my Custom subscriber:
final class EmojiBeamerSubscriber<Input, Failure: Error>: Subscriber, Cancellable {
var subscription: Subscription?
let receiveValue: (Input) -> Void
init(receiveValue: @escaping (Input) -> Void) {
self.receiveValue = receiveValue
}
func receive(subscription: Subscription) {
self.subscription = subscription
subscription.request(.max(3)) // Here I send only 3 as max demand
}
func receive(_ input: Input) -> Subscribers.Demand {
receiveValue(input)
return .none
}
func receive(completion: Subscribers.Completion<Failure>) {
print("Will handle later:", completion)
}
func cancel() {
self.subscription?.cancel()
self.subscription = nil
}
}
extension Publisher {
func myCustomSink(receiveValueHandler: @escaping (Self.Output) -> Void) -> AnyCancellable {
let myCustomSubscriber = EmojiBeamerSubscriber<Self.Output, Self.Failure>(receiveValue: receiveValueHandler)
subscribe(myCustomSubscriber)
return AnyCancellable(myCustomSubscriber)
}
}
As you can see on my custom subscription I request with demand .max(3) if I don't use collect everything works fine, I get an emoji beamed every 5 second after 3 I got a .finish completion.
Works fine (and sends .max(3) demand):
let emojiBeamer = EmojiBeamerPublisher()
var cancellables = Set<AnyCancellable>()
emojiBeamer
.myCustomSink { value in Swift.print("Random Emoji:: \(value)") }
.store(in: &cancellables)
However if I simply add .collect() to catch all 3 results at once in an array it just requests with .unlimited demand on my subscription, resulting in a never ending subscription because my demand will never reach zero.
Never complete (and sends unlimited demand):
let emojiBeamer = EmojiBeamerPublisher()
var cancellables = Set<AnyCancellable>()
emojiBeamer
.collect()
.myCustomSink { value in Swift.print("Random Emoji:: \(value)") }
.store(in: &cancellables)
Is there something wrong with my implementation? Or Did I misunderstood the purpose of .collect() operator?
Thank you in advance :)