6

I'm trying to write a custom Combine Publisher that will send decibel and timestamps from an AVAudioEngine tap. After going through numerous tutorials and the WWDC videos, I still can't find an example of how a Publisher keeps track of the Subscribers that have subscribed to it.

public typealias AudioVolume = Double

public struct AudioVolumePublisher: Publisher {
    public typealias Output = AudioVolume
    public typealias Failure = Error
}

public class AudioVolumeSubscription<S: Subscriber>: NSObject, Subscription {
    private var subscriber: S?
    public var combineIdentifier = CombineIdentifier()

    public init(for subscriber: S) {
        self.subscriber = subscriber
    }

    public func request(_ demand: Subscribers.Demand) {
        ...
    }

    public func cancel() {
        subscriber = nil
    }
}

I assume that the AudioVolumePublisher should store a list of its active subscribers, but adding a property like

var subscribers = [S]()

won't compile because Subscriber has associated types. Is this even the right approach to handling Subscribers, and if so, what's the best way to store them? Is type erasure my only practical option?

NRitH
  • 13,441
  • 4
  • 41
  • 44
  • Why don't you take a look at how [OpenCombine](https://github.com/broadwaylamb/OpenCombine) implements the operators? – Fabio Felici Jan 17 '20 at 18:58

1 Answers1

5

You may be able to achieve your goal with either CurrentValueSubject or PassthroughSubject.

Assuming this is the shape you are sending:

struct AudioVolume {
  let decimal: Decimal
  let timestamp: TimeStamp
}

instantiate subject

let audioPublisher = PassthroughSubject<AudioVolume, Never>()

whenever you need to publish a new value:

let audioVolume = AudioVolume(....)
audioPublisher.send(audioVolume)

whenever you need to subscribe to the publisher:

audioPublisher
  .sink { audioVolume in
    // do something with audioVolume
  }
  .store(in: &cancellables) // retain the subscription in cancellables as long as you want to remain subscribed
Gil Birman
  • 35,242
  • 14
  • 75
  • 119
  • 1
    How would this work with multiple `Subscriber`s, though? Can I simply call `audioPublisher.sink()` multiple times? `Publisher` is a protocol, so it can't keep track of subscribers itself, but `sink()`'s implementation must be doing something to keep the connection active. – NRitH Jan 17 '20 at 20:39
  • And what would the `cancellables`'s type be? – NRitH Jan 17 '20 at 20:40
  • 1
    @NRitH PassthroughSubject is a class, it has reference type semantics, if you subscribe multiple subscribers to it, it just keeps on ticking, if you see what I mean. – matt Jan 17 '20 at 21:01
  • 1
    Yes you can subscribe as many times as you want using `sink` or any other Combine operator. `cancellables` is type `Set`. – Gil Birman Jan 17 '20 at 21:24