合并 - 延迟发布者的发送
Posted
技术标签:
【中文标题】合并 - 延迟发布者的发送【英文标题】:Combine - Delay publisher's send 【发布时间】:2021-05-12 17:08:29 【问题描述】:当发布者在 Swift Combine 中发送一些数据时,最好的方法是什么?让我们假设以下情况:
private var publisher: PassthroughSubject<Progress, Error>
// closure called every second:
startWithProgress() [weak self] progress in
self.publisher.send(.init(progress: progress))
// How to call this 0.5 second after the above `send`:
self.publisher.send(.init(progress: progress + 0.5))
我检查了Delay
API,但似乎我需要创建另一个发布者来使用它,这在我的情况下是次优的。我还检查了 throttle
和 debounce
,但它们也不允许我一个接一个地发送 2 个更新,它们之间存在给定的延迟。
【问题讨论】:
根据您描述询问的方式,您可以将第二个电话包含在DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) ...
@NewDev 我认为有一种更“组合”的方式来做到这一点。比如:self.publisher.delay(.milliseconds(500)).send(.init(progress: progress + 0.5))
。基于此publisher
发送的值,我正在更新视图模型中的一些@published
属性,然后SwiftUI 视图将使用这些属性。我应该假设没有比直接使用 GCD 更好的方法吗?
重点是self.publisher.send
不是Combine。这是你在说话,而不是Combine。而你延迟半秒的方式就是半秒后做,这就是asyncAfter
所做的。
@matt 我同意是我在说,不是Combine,但send
是Subject
属于Combine 框架的方法。我认为有一些方法可以使用 Combine 的 API 和概念来实现我所需要的。为您提供更多背景知识 - 在我的应用程序中,我需要与一些旧 API 进行交互,我还需要在回调中插入从这些 API 获得的结果,然后根据这些插入结果更新 SwiftUI 视图。
【参考方案1】:
延迟发送值的行为(您可以使用DispatchQueue.asyncAfter
)与创建延迟上游值的组合管道之间存在区别。
你没有详细说明你真正想要完成的事情,所以很难给出明确的答案。
如果我要概括一下,看起来您想要一个管道,它为每个上游值发出值,然后再次发出值 + 0.5
,但延迟了。这可以像下面这样完成,例如:
let duplicateAndDelay = publisher
.flatMap [($0, 0), ($0 + 0.5, 0.5)].publisher // duplicate
.flatMap (progress, delay) in
Just(progress)
.delay(for: .seconds(delay), scheduler: RunLoop.main) // delay
那你可以send
一次:
startWithProgress() [weak self] progress in
self?.publisher.send(progress)
并返回要订阅的 duplicateAndDelay
发布者,而不是 publisher
发布者。
【讨论】:
谢谢!这正是我所需要的。 为了让它在 ios13 上运行,我必须将delay
中的 scheduler
更改为:.delay(for: .seconds(delay), scheduler: DispatchQueue.main)
。 TBH 我不确定为什么它在带有RunLoop.main
的 iOS 13 上不起作用。【参考方案2】:
从您分享的内容来看,我不会使用 Combine。 DispatchQueue.asyncAfter(deadline:execute:)
好像够用了。
如果必须的话,可以使用Publisher.delay(for:tolerance:scheduler:options:)
:
let subject = PassthroughSubject<Progress, Error>()
subject.delay(for: .seconds(5), scheduler: RunLoop.main)
.replaceError(with: .init())
.sink progress in
self.publisher.send(.init(progress: progress + 0.5))
.store(in: &cancellables)
甚至Publisher.publish(every:tolerance:on:in:options:)
Timer
.publish(every: 5, on: .main, in: .default)
.autoconnect()
.first()
.sink _ in
self.publisher.send(.init(progress: progress + 0.5))
或者,如果在您的情况下有意义,则在 @Published
变量中添加 progress
并使用它来启动管道
【讨论】:
我尝试像在您的示例中那样使用delay
,但我得到“元组类型'()'的值没有成员'延迟'。在send
之前添加delay
也没有' t 工作,因为它返回Publishers.Delay<PassthroughSubject<Progress, Error>, RunLoop>
,不能在其上调用send
方法。
我的错,我编辑了我的答案,诀窍是打电话给replaceError(with:)
:Publishers.ReplaceError.Failure = Never
。 developer.apple.com/documentation/combine/publishers/…【参考方案3】:
我没有对此进行测试,使用计时器可能更有意义。你需要将cancellable存储在一个实例变量中,这样当函数返回时它就不会消失。
var cancellable = self.publisher
.delay(for: .seconds(0.5), scheduler: RunLoop.main )
.sink(receiveCompletion: _ in
// do something with the error or completion
, receiveValue: [unowned self] progress in
// you don't show any code that actually updates progress, without that this will just keep sending the initial value + 0.5.
self.publisher.send(.init(progress: progress + 0.5))
)
self.publisher.send(.init(progress: progress))
【讨论】:
不测试很危险 ;)Referencing instance method 'sink(receiveValue:)' on 'Publisher' requires the types 'Error' and 'Never' be equivalent
不错,点赞 ;) .sink(receiveCompletion: _ in
否则无法编译 ;)
它似乎也在延迟第一个值,这不是 OP 想要的
OP 调用已发送 2 次,我认为这是第二次
如果你只想发送两次Just
是要走的路,就像@NewDev 的回答一样。我猜你不想在延迟后对失败做点什么,而是在出现错误时什么都不做。以上是关于合并 - 延迟发布者的发送的主要内容,如果未能解决你的问题,请参考以下文章