In a DispatchSource
, the duration is effectively the time it takes the event handler to run. If the event handler takes 100ms to run, then incoming events are coalesced during that period. Once the event handler exists, then GCD can invoke it again.
GCD offers minimal support for passing in a Uint
value to the event handler. In a newer framework, it would be great if the value passed in could be a richer data type and perhaps customizable so that the caller could specify if they want the first, last or some other value that was received while the event handler has busy.
Unlike Debounce
and Throttle
, there is no explicit duration or elapsed time between invocations of the event handler. It should be called as frequently as possible.
It's basically a serial queue implemented such that when a new block is pushed into the queue, any pending blocks are cancelled. Thus there's never really any more than one block executing and one block potentially pending.
After playing around a decent amount with Combine publishers, I was never able to truly match the behaviour of a DispatchSource. That makes me think that perhaps the implementation is different enough that it warrants its own operator.
There was some discussion on Stack Overflow here, though even this amazing answer isn't quite the same as a DispatchSource:
How do you apply a Combine operator only after the first message has been received?
I would consider it more for a reducer because I want events that occur while the event handler is running to be reduced into a single event.
For example, if I'm busy filtering a list on a background thread and the user continues to modify the query parameter, then I only need to know about the latest value of the filter query. This is, clearly, what Debounce
does but Debounce
and Throttle
both include some concept of elapsed time before they pass on their values to a listener. It's that delay that a DispathSource
doesn't have which is appealing to me.
"abcd----e---f-g----|"
"a---d---e---f-g----|"
Not sure if that's the correct way to draw such a diagram, but consider the following pseudo-code:
for await value in input.coalesce() {
// While this block of code is running, coalesce any input
// events into a single element.
// When this block exits, `input` should have either one
// pending element that should be processed immediately
// or it should be empty and we wait for the next value to
// be generated.
}
Two concrete examples of where this pattern is useful might be the Spotlight window in the Finder and Xcode's Quick Open Panel. In both cases, you want to show results to the user as quickly as possible so you should respond to any initial input right away. (It might be the only input.)
As the user types more, the "filtering thread" tries to keep up in real time as much as possible but it's ok if it falls behind the user's typing speed. Key events that occur while a filter is in-progress should just be coalesced together into a single event that can be processed on the next invokation of the filtering event handler. At most, there's only ever one pending event.