Thanks again everyone for the feedback. Continuing a few of the items that were brought up:
AsyncMessage and Synchronous, Non-MainActor Posting
It's true that Message & Sendable
is enough to have an observer run synchronously on the thread of the poster, but this isn't too different from the status quo today: addObserver()
with Notification
will run an observer in a synchronous, non-isolated context, requiring a developer who wishes to mutate state in response to a Notification
to either only interact with other non-isolated variables, or spin up a Task
to mutate state. For messages and observers that run on MainActor
, MainActorMessage
is a good solution. For background posters, it's a little trickier.
We're constrained by existing uses of post()
which may be on a background thread and which expect post()
to be a synchronous function, looping through all observers synchronously to call them.
To account for this, we synchronously enqueue the observers to be processed by a single Task
, maintained by NotificationCenter
. This results in post()
remaining synchronous, while the Task
can run each observer.
If we inherited the isolation of the observer declaration site, we would need a way to enqueue the observer call onto the observer declaration site's isolation synchronously (to ensure post()
remains synchronous), or we would need to continue to use our existing Task
solution, but make a second isolation hop onto the observer declaration site's isolation.
makeNotification()/makeMessage() and Subject
Message
and Notification
are not quite equivalent to each other - while Notification
packages userInfo
and object
together, many use cases of Notification
have a nil
object, or only ever use object
to filter notifications, but don't otherwise use the object
in other logic. This is one of the reasons we've designed Message
to consider Subject
as something separate from the Message
. Developers are free to re-use Subject
in Message
if it makes sense.
At the moment, makeNotification()
is not expected to fill in notification.object
, opting instead to favor the subject
passed in to post()
. We'll have to consider this behavior further, particularly for the use cases where a developer is ferrying their subject
within Message
. Perhaps it would make more sense to have makeNotification(from: message, with: subject)
but keep makeMessage(from: notification)
?
MainActorMessage vs @MainActor
@MainActor
does not participate in Swift generics, so we're unable to offer an overload of addObserver()
based on a type with this annotation alone.
ObservationToken and non-copyable
If ObservationToken
was non-copyable, we would likely want its deinit
to remove the observer registration, but that would mean letting the token go out of scope would result in observers being removed "implicitly", which could cause bugs. I think that unfortunate behavior may be worse than the benefit of having a non-copyable ObservationToken
.