自定义合并发布器,用于单个数据库监听器

huangapple go评论131阅读模式
英文:

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来检索我的项目,我希望在创建新的订阅时立即设置数据库监听器(例如当调用sinkassign时),并在没有更多订阅时取消它。

因此,我想到了以下解决方案:

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&#39;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&lt;[Item], Never&gt;)?
    private var listener: DatabaseListener?
    private var items: [Item] = UserDefaults.standard.cacheItems

    init(subscriber: any Subscriber&lt;[Item], Never&gt;) {
        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&lt;S&gt;(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&lt;AnyCancellable&gt; = []

ItemsPublisher()
    .sink { items in

    }
    .store(&amp;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.

huangapple
  • 本文由 发表于 2023年2月17日 23:31:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75486267.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定