Friday, November 14, 2025

NotificationQueue and Custom Dispatch Source Coalescing

I have some old code that uses NSNotificationQueue to coalesce notifications. I think this is an underappreciated class. (Even in the old days, I saw a lot more talk about +cancelPreviousPerformRequestsWithTarget:selector:object:.) My newer code uses more threads, but notification queues are not thread-safe. You can create additional queues beyond the default and have each one be thread-isolated, but the delayed posting is based around the run loop. That doesn’t play so well with GCD and now Swift Testing.

NotificationCenter got some shiny new APIs in Swift 6.2, but there was no love for NotificationQueue. I’m not sure it’s gotten any documented improvements in the entire time I’ve been programming Cocoa.

My first thought was to write a simple helper that would post notifications asynchronously. After enqueueing a posting block, it would suppress any further posts until it observed the arrival of its own notification.

But it turns out that GCD already has a built-in solution to do stuff like this, and it’s probably more efficient. If you create a custom dispatch source, it will automatically coalesce:

To prevent events from becoming backlogged in a dispatch queue, dispatch sources implement an event coalescing scheme. If a new event arrives before the event handler for a previous event has been dequeued and executed, the dispatch source coalesces the data from the new event data with data from the old event. Depending on the type of event, coalescing may replace the old event or update the information it holds.

Using this API looks a little different in Objective-C vs. Swift. In Swift, to set up and receive notifications, you can do something like:

source = DispatchSource.makeUserDataAddSource(queue: .main)
source.setEventHandler() {
    center.post(notification)
}
source.resume()

Note that you can post the notifications on whichever queue you want. The main queue works well for coalescing UI updates.

To enqueue a notification, from any thread, you can just use:

source.add(data: 1)

If you want, within the event handler, you can access source.data to see how many updates were coalesced.

I’m pretty sure this is the most modern way to do event coalescing. I didn’t see anything related in Swift Concurrency, though you could certainly use that to implement your own solution.

Previously:

Comments RSS · Twitter · Mastodon

Leave a Comment