合并 - 延迟发布者的发送

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,但似乎我需要创建另一个发布者来使用它,这在我的情况下是次优的。我还检查了 throttledebounce,但它们也不允许我一个接一个地发送 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,但sendSubject 属于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&lt;PassthroughSubject&lt;Progress, Error&gt;, RunLoop&gt;,不能在其上调用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 的回答一样。我猜你不想在延迟后对失败做点什么,而是在出现错误时什么都不做。

以上是关于合并 - 延迟发布者的发送的主要内容,如果未能解决你的问题,请参考以下文章

合并复制可以以 0 毫秒的延迟运行,即实时吗?

如何避免使用 API 延迟 github 拉取请求合并

QWebSocketServer 发送延迟

在 iOS 中添加发送 SMS 的延迟

使用延迟作业发送电子邮件时报告错误

Socket.IO 延迟发送