0

I want to debounce a stream of values, and add a flush signal to it. When the flush signal comes, debounce should immediately emit its last received value and clear its buffer. If there is no value in debounce's buffer, the flush signal does nothing. Basically, the flush signal temporarily sets the debounce time to 0:

values: -- 1 -- 2 -- 3 ---------- 4 -- 5 -- 6 --------------------

flush:  --------------------------------- * -------------- * -----

output: ------------------- 3 ----------- 5 ------- 6 ------------

I tried the following approach, mimicking debounce with delay and switchToLatest, but couldn't figure out how to do flushedValues, which should emit the last value, if any, that comes within 2 seconds before a flush signal.

// valuesSubject: Publisher<Int, Never> is the value stream
// flushSubject: Publisher<Void, Never> is the flush stream

let valuesWithDelay = valuesSubject.map { $0.delay(for: 2, scheduler: ...) }
let flushedValues = // somehow combine valuesSubject and flushSubject
let output = Publishers.Merge(valuesWithDelay, flushedValues).switchToLatest()
Jack Guo
  • 3,959
  • 8
  • 39
  • 60

1 Answers1

0

The solution I came up with is below.

For each value that is emitted it creates a Future that will resolve when sufficient time has passed or when flush is sent.

These futures are then passed to switchToLatest. If you emit values quickly (below the debounce threshold) then the newest future will replace any older ones. Only the "latest" has a chance to emit a value.

import Cocoa
import Combine
import PlaygroundSupport
import SwiftUI

let flush = PassthroughSubject<Void, Never>()

let values = Timer.publish(every: 0.1, on: .main, in: .common)
    .autoconnect()
    .scan(0) { prev, _ in prev + 1}
    .filter { $0 % 10 < 3 }

let output = values
    .map { value in
        return Future<Int, Never> { fulfill in
            var flushValue : AnyCancellable?
            var waitForValue : AnyCancellable?

            flushValue = Just(value)
                .combineLatest(flush) { value, _ in return value }
                .sink {
                    waitForValue?.cancel()
                    fulfill(.success($0))
                }

            waitForValue = Just(value)
                .delay(for: 0.3, scheduler: RunLoop.main)
                .sink (
                    receiveValue: {
                    flushValue?.cancel()
                    fulfill(.success($0))
                })
        }
    }
    .switchToLatest()
    .sink { debugPrint($0) }

struct FlushButtonView : View {
    var body : some View {
        Button("Flush") { flush.send(()) }
    }
}

PlaygroundSupport.PlaygroundPage.current.liveView = NSHostingController(rootView: FlushButtonView())
Scott Thompson
  • 22,629
  • 4
  • 32
  • 34