【问题标题】: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 视图。

标签: ios swift swiftui combine


【解决方案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 = Neverdeveloper.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 的回答一样。我猜你不想在延迟后对失败做点什么,而是在出现错误时什么都不做。
猜你喜欢
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 2017-08-07
  • 1970-01-01
  • 2012-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多