You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Update concurrency-safe notifications proposal (v4)
* Update status for SF-0011 for second review
---------
Co-authored-by: Charles Hu <[email protected]>
***v2** Remove `static` from `NotificationCenter.Message.isolation` to better support actor instances
12
12
***v3** Remove generic isolation pattern in favor of dedicated `MainActorMessage` and `AsyncMessage` types. Apply SE-0299-style static member lookups for `addObserver()`. Provide default value for `Message.name`.
13
+
***v4** Add `AsyncSequence` APIs for observing. Expand `Message.Subject` conformance to take either `AnyObject` or `Identifiable` where `Identifiable.ID == ObjectIdentifier`. Document `ObservationToken` automatic de-registration behavior. Drop `with` label on `post()` methods in favor of `subject` for clarity.
@@ -194,10 +195,14 @@ The protocol specifies `makeMessage(:Notification)` and `makeNotification(:Self)
194
195
195
196
For `Message` types that do not need to interoperate with existing `Notification` uses, the `name` property does not need to be specified, and will default to the fully qualified name of the `Message` type, e.g. `MyModule.MyMessage`. Note that when using this default, renaming the type or relocating it to another module has a similar effect as changing ABI, as any code that was compiled separately will not be aware of the name change until recompiled. Developers can control this effect by explicitly setting the `name` property if needed.
196
197
198
+
Each `Message` specifies a specific *subject* variable or metatype to observe, similar to the existing `Notification.object`, e.g. an `NSWindow` instance or the `NSWindow.self` metatype. `Message.Subject` has no conformance requirements in its protocol, but `addObserver()` and `post()` both refine `Message.Subject` to either conform to `AnyObject` or confirm to `Identifiable` where `Identifiable.ID == ObjectIdentifier`.
199
+
197
200
### Observing messages
198
201
199
202
Observing messages can be done with new overloads to `addObserver`. Clients do not need to know whether a message conforms to `MainActorMessage` or `AsyncMessage`.
200
203
204
+
Overloads are provided both for `Message.Subject: AnyObject` and `Message.Subject: Identifiable where ID == ObjectIdentifier`. This allows the observation of both reference types and value types which can provide an `ObjectIdentifier`.
When an `ObservationToken` goes out of scope, the corresponding observer will be removed from its center automatically if it is still registered. This behavior helps prevent memory leaks from tokens which are accidentally dropped by the user.
295
+
296
+
Messages conforming to `AsyncMessage` can also be observed using a set of `AsyncSequence`-conforming APIs, similar to the existing `notifications(named:object:)` method:
-> some AsyncSequence<Message, Never>where Identifier.MessageType== Message
324
+
325
+
publicfuncmessages<Message: AsyncMessage>(
326
+
ofsubject: Message.Subject?=nil,
327
+
formessageType: Message.Type,
328
+
bufferSizelimit: Int=10
329
+
)
330
+
-> some AsyncSequence<Message, Never>where Message.Subject:AnyObject
331
+
332
+
publicfuncmessages<Message: AsyncMessage>(
333
+
ofsubject: Message.Subject?=nil,
334
+
formessageType: Message.Type,
335
+
bufferSizelimit: Int=10
336
+
)
337
+
-> some AsyncSequence<Message, Never>where Message.Subject: Identifiable,
338
+
Message.Subject.ID==ObjectIdentifier
339
+
}
340
+
```
341
+
342
+
These allow for the familiar `for await in` syntax:
343
+
344
+
```swift
345
+
forawait message in center.messages(of: anObject, for: .anAsyncMessage) {
346
+
// ...
347
+
}
348
+
349
+
forawait message in center.messages(for: AnAsyncMessage.self) {
350
+
// ...
351
+
}
352
+
353
+
// etc.
354
+
```
355
+
356
+
The `messages()` sequence uses a reasonably-sized buffer to reduce the likelihood of dropped messages caused by the interaction of synchronous and asynchronous code. When a `Message` is dropped, the implementation will log to aid in debugging. Message frequency in practice is typically 0-2x / second / message type and therefore unlikely to result in dropped messages. Certain UI-related messages can post in practice as often as 40 - 50x / second / message, but these are typically `MainActorMessage` and would not be subject to dropping nor available for use with `messages()`.
357
+
261
358
### Posting messages
262
359
263
360
Posting messages can be done with new overloads on the existing `post` method:
264
361
265
362
```swift
266
363
@available(FoundationPreview 0.5, *)
267
364
extensionNotificationCenter {
268
-
publicfuncpost<M: Message>(_message: M, withsubject: M.Subject)
269
-
publicfuncpost<M: Message>(_message: M, withsubject: M.Subject.Type)
365
+
366
+
// MainActorMessage post()
367
+
368
+
@MainActor
369
+
publicfuncpost<M: MainActorMessage>(_message: M, subject: M.Subject)
370
+
where M.Subject:AnyObject
371
+
372
+
@MainActor
373
+
publicfuncpost<M: MainActorMessage>(_message: M, subject: M.Subject)
374
+
where M.Subject: Identifiable,
375
+
M.Subject.ID==ObjectIdentifier
376
+
377
+
@MainActor
378
+
publicfuncpost<M: MainActorMessage>(_message: M, subject: M.Subject.Type= M.Subject.self)
379
+
380
+
// AsyncMessage post()
381
+
382
+
publicfuncpost<M: AsyncMessage>(_message: M, subject: M.Subject)
383
+
where M.Subject:AnyObject
384
+
385
+
publicfuncpost<M: AsyncMessage>(_message: M, subject: M.Subject)
386
+
where M.Subject: Identifiable,
387
+
M.Subject.ID==ObjectIdentifier
388
+
389
+
publicfuncpost<M: AsyncMessage>(_message: M, subject: M.Subject.Type= M.Subject.self)
However, not all messages have subject instances (e.g. `addObserver(of: NSWindow.self, for: .willMove)`). While `post()` could take a default parameter for an optional `subject`, the `addObserver()` closure would always have to specify a `subject` parameter even for messages without subject instances.
0 commit comments