英文:
Custom Combine Publisher for a single database listener
问题
我有一个带有自定义数据库的iOS应用程序,为了从我的数据库中检索数据,我设置了一个监听器,如下所示:
var listener: DatabaseListener?
self.listener = db.items.addListener { items in
}
// later when I don't need the listener anymore:
self.listener?.cancel()
这个监听器在我设置它后立即提供项目,并在我的数据更新时通知我。它也会一直保持活动状态,直到我手动取消它。我还在UserDefaults中存储了检索到的项目的缓存,以加速操作(在下面的示例中查看它的运行方式)。
现在我尝试开始使用Combine来检索我的项目,我希望在创建新的订阅时立即设置数据库监听器(例如当调用sink或assign时),并在没有更多订阅时取消它。
因此,我想到了以下解决方案:
class ItemsSubscription: Subscription {
private var subscriber: AnySubscriber<[Item], Never>?
private var listener: DatabaseListener?
private var items: [Item] = UserDefaults.standard.cacheItems
init(subscriber: AnySubscriber<[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)
}
}
然后,我使用ItemsPublisher如下:
private var cancellables: Set<AnyCancellable> = []
ItemsPublisher()
.sink { items in
}
.store(&cancellables)
目前,这种方法有效,但它为每个我创建的ItemsPublisher创建了一个新的数据库监听器(这是一种昂贵的资源)。相反,我希望在有至少一个订阅者时保持单个数据库监听器,并希望任何后续的订阅者从同一订阅接收到最新的项目。
我考虑过创建一个单个的ItemsPublisher实例并在整个应用程序中使用它,但后来的订阅者根本不接收任何数据。
我还考虑过使用CurrentValueSubject(或@Published属性)来存储项目,但我无法弄清楚何时设置数据库监听器,或者什么时候取消它。希望这些信息对你有所帮助。
英文:
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.
答案1
得分: 1
> 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.
这是share()的用途。 查看文档以获取更多信息。
你可能还希望根据情况考虑使用具有CurrentValueSubject的多播。
英文:
> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论