0

I'm trying to write a custom publisher that generates some values. Something like this:

class MyPublisher: Publisher {

    typealias Output = Int
    typealias Failure = Never

    private let subject = PassThroughSubject<Int, Never>()

    func receive<S>(subscriber: S) where S: Subscriber, S.Failure == Failure, S.Input == Output {
        subject.receive(subscriber: subscriber)
        startSending()
    }

    func startSending() {
        subject.send(1)
        subject.send(2)
        subject.send(3)
        subject.send(completion: .finished)
    }
}

I'm trying to figure out how to call startSending() automatically after a subscribing attaches, but I'm not sure if I'm doing it right.

I've just been reading about ConnectablePublisher and was wondering if that might help, but I'm not sure how.

Has anyone tried something like this? How did you do it?

drekka
  • 20,957
  • 14
  • 79
  • 135
  • 1
    I don’t know the exact answer, it’s been a while since I’ve worked with this, but I can say confidently: the WWDC keynotes are some of the best documentation on combine, particularly conceptual things like this, about how exactly publisher life times work. I’d suggest you have a watch through those, they’re pretty great! – Alexander Sep 07 '21 at 14:26
  • 1
    You're not actually doing that which is the job of a custom publisher. See my https://www.apeth.com/UnderstandingCombine/publishers/publisherscustom.html. – matt Sep 07 '21 at 14:57
  • 1
    @matt Can't we just take the PassthroughSubject `subject` and do a `subject.receive(subscriber: subscriber)` in function receive in order to make this a complete custom publisher? – CouchDeveloper Sep 07 '21 at 15:44
  • 2
    This does what you're trying to do. As soon as it receives a subscriber, it will startSending and the subscriber will receive the updates. BUT, since you're also sending completion, only the first subscriber will receive the updates. – farzadshbfn Sep 07 '21 at 15:59
  • 1
    When you add a subscriber to your publisher, `startSending` will be called and the subscriber receives 3 values, then "finished". Subsequent subscribers only receive a "finished" - because your internal subject is finished. Is this what you want? (ups, basically said the same as farzadshbfn ;) – CouchDeveloper Sep 07 '21 at 16:04
  • 2
    The problem is that I don't know what you're _really_ trying to do. If the goal were really to publish 1, then 2, then 3, then complete, you don't need to make a custom publisher to do that. If you want better help, ask a more realistic question. – matt Sep 07 '21 at 16:30
  • Thanks everyone. This is a very simplified version of the real code I'm working with so it does look a little contrived. As I said, what I'm trying to do is work out the best way to initiate sending, but only after a subscriber is attached. Hope this helps and thanks again. It's all food for thought. – drekka Sep 08 '21 at 14:52
  • @CouchDeveloper I have messed about with a full on custom publisher as per your blog post previously, but I've generally found that using an internal subject like this solves most situations. I might still end up going the whole hog though :-) – drekka Sep 08 '21 at 15:04
  • Just as info: as, matt already pointed out, your final goal is not clear. The above custom publisher can be simulated with just `[1, 2, 3].publisher` :) – CouchDeveloper Sep 08 '21 at 15:32
  • "initiate sending, but only after a subscriber is attached." ... have you looked into [`Deferred`](https://developer.apple.com/documentation/combine/deferred) – New Dev Sep 08 '21 at 22:33

1 Answers1

0

As a continuation of this. One of the changes I've been experimenting with is to locally declared the subject instead of having it as a class variable. I think that solves the issue some raised about multiple subscribers.

public func receive<S>(subscriber: S) where S: Subscriber, S.Failure == Failure, S.Input == Output {
    let subject = PassthroughSubject<Int, Error>()
    defaultValueSubject.receive(subscriber: subscriber)
    startSending(to: subject)
}

func startSending(to subject: PassThroughSubject<Int>) {
    subject.send(1)
    subject.send(2)
    subject.send(3)
    subject.send(completion: .finished)
}
drekka
  • 20,957
  • 14
  • 79
  • 135