I have an iOS app with a custom database, To retrieve data from my database I setup a listener like this:
var listener: DatabaseListener?
self.listener = db.items.addListener { items in
}
// later when I don't need the listener any more:
self.listener?.cancel()
This listener gives the items as soon as I set it up and notifies me whenever my data is updated, It also stays alive until I manually cancel it. I also store a cache of the retrieved items in UserDefaults
to speed things up (See it in action in the example bellow).
Now I'm trying to start using Combine
to retrieve my items, I want to setup the database listener as soon as a new subscription is created (for example when sink
or assign
are called) and cancel it when there's no more subscriptions left.
So here's what I came up with:
class ItemsSubscription: Subscription {
private var subscriber: (any Subscriber<[Item], Never>)?
private var listener: DatabaseListener?
private var items: [Item] = UserDefaults.standard.cacheItems
init(subscriber: any Subscriber<[Item], Never>) {
self.subscriber = subscriber
}
func request(_ demand: Subscribers.Demand) {
let _ = subscriber?.receive(items)
self.listener = db.items.addListener {
UserDefaults.standard.cacheItems = $0
self.items = $0
let _ = self.subscriber?.receive($0)
}
}
func cancel() {
self.listener?.cancel()
self.listener = nil
self.subscriber = nil
}
}
struct ItemsPublisher: Publisher {
typealias Output = [Item]
typealias Failure = Never
func receive<S>(subscriber: S) where S: Subscriber, S.Input == [Item], S.Failure == Never {
let subscription = ItemsSubscription(subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
Then I'm using ItemsPublisher
like this:
private var cancellables: Set<AnyCancellable> = []
ItemsPublisher()
.sink { items in
}
.store(&cancellables)
Currently this method is working but it's creating a new database listener (which is an expensive resource) for every ItemsPublisher
I create. Instead I want to maintain a single database listener while I have a least 1 subscriber and I want any following subscriber to receive the latest items from the same subscription.
I considered creating a single ItemsPublisher
instance and using it throughout the app, but later subscribers didn't receive any data at all.
I also considered using CurrentValueSubject
(or a @Published
property) to store the items but I couldn't figure out when to setup database listener or when to cancel it for that matter.
Any help or advice would be appreciated.
Instead I want to maintain a single database listener while I have a least 1 subscriber and I want any following subscriber to receive the latest items from the same subscription.
That's exactly what share()
is for. View the documentation for more information.
You might also want to consider using multicast with a CurrentValueSubject depending on the situation.