【问题标题】:How to compose a combine publisher that emits the last-emitted value to its 2nd subscriber如何组成一个组合发布者,将最后发出的值发送给它的第二个订阅者
【发布时间】:2020-12-21 01:54:40
【问题描述】:

我正在尝试编写一个发布者,当后续订阅者连接到它时,它会立即向该订阅者发出最后一个发出的值,然后继续向所有连接的订阅者发出未来的值。

问:有没有办法做到这一点无需从头开始编写发布者,而只需编写内置发布者?我可能在这里遗漏了一些东西。

所以,如果我们有一个出版商每秒计数:

let counter = Timer.publish(every: 1, on: .main, in: .common)
                   .autoconnect()
                   .scan(0, { v, _ in v + 1 })


let sharedPublisher = // ??? something with counter publisher above

sharedPublisher.sink { print("A: ", $0 }.store(in: &bag)

// after 2.5 seconds
sharedPublisher.sink { print("B: ", $0 }.store(in: &bag)

输出将是:

A: 1 // at t=1 sec
A: 2 // at t=2
B: 2 // at t=2.5
A: 3 // at t=3
B: 3 // at t=3

起初,我天真地以为我可以只使用.share.buffer

let sharedPublisher = counter
                  .share()
                  .buffer(size: 1, prefetch: .byRequest, whenFull: .dropOldest)

但当然,这不起作用,因为buffer 仅在下游尚未准备好接受值时才缓冲,这里不是这种情况。 可能是Record/Record.Recording

【问题讨论】:

  • 向新订阅者提供其最新值的发布者是 CurrentValueSubject。
  • @matt,我认为CurrentValueSubject 总是在其初始化中给出最初提供的值。它实际上给出了最后一个发出的?编辑:你是对的!你能写一个关于如何连接它以获得上面的输出的答案吗?

标签: swift combine


【解决方案1】:

一个可以共享的发布者(因为它是一个类,而不是一个结构)并且可以立即将它的最新值传递给任何新订阅者,它的最新值是 CurrentValueSubject。因此,例如,这与您所做的不完全一样,但它证明了这一点:

import UIKit
import Combine

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController: UIViewController {
    var timer : Timer?
    let sub = CurrentValueSubject<Int,Never>(0)
    var storage = Set<AnyCancellable>()
    override func viewDidLoad() {
        super.viewDidLoad()
        self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.sub.value += 1
        }
        delay(0.5) {
            self.sub.sink { print("A:", $0) }.store(in: &self.storage)
        }
        delay(2.5) {
            self.sub.sink { print("B:", $0) }.store(in: &self.storage)
        }
    }
}

输出

A: 0
A: 1
A: 2
B: 2
A: 3
B: 3

这似乎正是您想要的效果。

很酷的一点是,Subject 也可以是运算符(通过发布者的.subscribe 方法),因此它可以存在于其他事物的下游,同时保持这种行为。唯一的技巧是您必须保留主题(您可以这样做,因为.subscribe 调用会产生一个 AnyCancellable)。所以现在我可以按照你的方式来做,即从计时器发布者开始:

import UIKit
import Combine

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController: UIViewController {
    var storage = Set<AnyCancellable>()
    override func viewDidLoad() {
        super.viewDidLoad()
        let counter = Timer.publish(every: 1, on: .main, in: .common)
                           .autoconnect()
                           .scan(0, { v, _ in v + 1 })
        let sub = CurrentValueSubject<Int,Never>(0)
        counter.subscribe(sub).store(in: &self.storage)
        delay(0.5) {
            sub.sink { print("A:", $0) }.store(in: &self.storage)
        }
        delay(2.5) {
            sub.sink { print("B:", $0) }.store(in: &self.storage)
        }
    }
}

【讨论】:

  • 酷...我必须添加.dropFirst() 以删除第一个订阅者获得的初始值(在我的情况下是未知/任意),但否则它可以工作!
  • 等等,还有更多!坚持一下。
  • 好了,完成了! :)
  • 仍在努力将其变成一个简洁的运算符,例如 somePublisher.shareLast(),因为需要存储订阅
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多