Swift Combine: Buffer upstream values and emit them at a steady rate?

后端 未结 4 942
被撕碎了的回忆
被撕碎了的回忆 2021-02-06 04:16

Using the new Combine framework in iOS 13.

Suppose I have an upstream publisher sending values at a highly irregular rate - sometimes seconds or minutes may go by withou

4条回答
  •  借酒劲吻你
    2021-02-06 04:47

    EDIT

    There's an even simpler approach to the original one outlined below, which doesn't require a pacer, but instead uses back-pressure created by flatMap(maxPublishers: .max(1)).

    flatMap sends a demand of 1, until its returned publisher, which we could delay, completes. We'd need a Buffer publisher upstream to buffer the values.

    // for demo purposes, this subject sends a Date:
    let subject = PassthroughSubject()
    let interval = 1.0
    
    let pub = subject
       .buffer(size: .max, prefetch: .byRequest, whenFull: .dropNewest)
       .flatMap(maxPublishers: .max(1)) {
          Just($0)
            .delay(for: .seconds(interval), scheduler: DispatchQueue.main)
       }
    

    ORIGINAL

    I know this is an old question, but I think there's a much simpler way to implement this, so I thought I'd share.

    The idea is similar to a .zip with a Timer, except instead of a Timer, you would .zip with a time-delayed "tick" from a previously sent value, which can be achieved with a CurrentValueSubject. CurrentValueSubject is needed instead of a PassthroughSubject in order to seed the first ever "tick".

    // for demo purposes, this subject sends a Date:
    let subject = PassthroughSubject()
    
    let pacer = CurrentValueSubject(())
    let interval = 1.0
    
    let pub = subject.zip(pacer)
       .flatMap { v in
          Just(v.0) // extract the original value
            .delay(for: .seconds(interval), scheduler: DispatchQueue.main)
            .handleEvents(receiveOutput: { _ in 
               pacer.send() // send the pacer "tick" after the interval
            }) 
       }
    

    What happens is that the .zip gates on the pacer, which only arrives after a delay from a previously sent value.

    If the next value comes earlier than the allowed interval, it waits for the pacer. If, however, the next value comes later, then the pacer already has a new value to provide instantly, so there would be no delay.


    If you used it like in your test case:

    let c = pub.sink { print("\($0): \(Date())") }
    
    subject.send(Date())
    subject.send(Date())
    subject.send(Date())
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
       subject.send(Date())
       subject.send(Date())
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
       subject.send(Date())
       subject.send(Date())
    }
    

    the result would be something like this:

    2020-06-23 19:15:21 +0000: 2020-06-23 19:15:21 +0000
    2020-06-23 19:15:21 +0000: 2020-06-23 19:15:22 +0000
    2020-06-23 19:15:21 +0000: 2020-06-23 19:15:23 +0000
    2020-06-23 19:15:22 +0000: 2020-06-23 19:15:24 +0000
    2020-06-23 19:15:22 +0000: 2020-06-23 19:15:25 +0000
    2020-06-23 19:15:32 +0000: 2020-06-23 19:15:32 +0000
    2020-06-23 19:15:32 +0000: 2020-06-23 19:15:33 +0000
    

提交回复
热议问题