Wednesday, October 29, 2025

Swift 6.2: NotificationCenter Messages

Holly Borla:

In Swift 6.2, the Foundation library includes a modern NotificationCenter API that uses concrete notification types instead of relying on strings and untyped dictionaries for notification names and payloads. This means you can define a notification struct with stored properties, and observers can use the type without error-prone indexing and dynamic casting.

Apple:

In Swift, the Notification type is nonisolated, even in cases where it’s posted on a known isolation. To provide specific isolation information and better support Swift concurrency, NotificationCenter defines two message types. A NotificationCenter.MainActorMessage binds to the main actor, whereas a NotificationCenter.AsyncMessage uses an arbitrary isolation. Frameworks extend these types to define distinct messages, typically corresponding to an existing Notification.Name, that declare instance properties for their values instead of using a userInfo dictionary. As a result, messages can conform to Sendable when they either don’t use properties or contain only sendable properties.

If your project only needs to support Swift, you can just use the Message types. For projects with both Objective-C and Swift code, define a Notification as well as a corresponding Message type.

However, the new API is part of Foundation, not Swift 6.2, so even in a pure-Swift app you can only use it on macOS 26 and later. This is a bummer because notifications aren’t a new feature that you can make available in your app only on the latest OS. Rather, they’re central to the design of the app itself. You can’t even begin migrating your code to the new API until you drop support for all the previous OS versions.

You can post a message with the post(_:subject:) method, passing a message instance and optionally providing a subject. To receive messages, add an observer with the addObserver(of:for:using:) method. The overloads of this method allow you to observe either messages from a single object or from any object of a given type. Observation ends when you discard the token returned from addObserver(of:for:using:) or after an explicit call to removeObserver(_:).

You can also receive messages as an AsyncSequence with the messages(of:for:bufferSize:) methods.

SF-0011:

The optional lookup type, NotificationCenter.MessageIdentifier, provides an SE-0299-style ergonomic experience for finding notification types when registering observers.

[…]

Optional bi-directional interoperability with the existing Notification type is available by using the Notification.Name property and two optional methods, makeMessage(:Notification) and makeNotification(:Self)[…]

[…]

Observers called via the existing, pre-Swift Concurrency .post() methods are either called on the same thread as the poster, or called in an explicitly passed OperationQueue.

However, users can still adopt Message-style types with pre-Swift Concurrency .post() calls by providing a Message-style type with the proper Notification.Name value and picking the correct type between MainActorMessage and AsyncMessage.

See also: Fatbobman.

Previously:

Comments RSS · Twitter · Mastodon

Leave a Comment