Skip to content

Commit 8aa6815

Browse files
authored
Merge branch 'swiftlang:main' into proposal/progress-reporter
2 parents 7d92d2d + bfc4580 commit 8aa6815

23 files changed

+3188
-358
lines changed

.github/workflows/automerge.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Create PR to merge main into release branch
2+
# In the first period after branching the release branch, we typically want to include many changes from `main` in the release branch. This workflow automatically creates a PR every Monday to merge main into the release branch.
3+
# Later in the release cycle we should stop this practice to avoid landing risky changes by disabling this workflow. To do so, disable the workflow as described in https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/disabling-and-enabling-a-workflow
4+
on:
5+
schedule:
6+
- cron: '0 9 * * MON'
7+
workflow_dispatch:
8+
jobs:
9+
create_merge_pr:
10+
name: Create PR to merge main into release branch
11+
uses: swiftlang/github-workflows/.github/workflows/create_automerge_pr.yml@main
12+
with:
13+
base_branch: release/6.2
14+
permissions:
15+
contents: write
16+
pull-requests: write
17+
if: (github.event_name == 'schedule' && github.repository == 'swiftlang/swift-foundation') || (github.event_name != 'schedule') # Ensure that we don't run this on a schedule in a fork

Benchmarks/Benchmarks/URL/BenchmarkURL.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -248,26 +248,26 @@ let benchmarks = {
248248
]
249249

250250
var variables: [URL.Template.VariableName: URL.Template.Value] = [
251-
"count": ["one", "two", "three"],
252-
"dom": ["example", "com"],
253-
"dub": "me/too",
254-
"hello": "Hello World!",
255-
"half": "50%",
256-
"var": "value",
257-
"who": "fred",
258-
"base": "http://example.com/home/",
259-
"path": "/foo/bar",
260-
"list": ["red", "green", "blue"],
261-
"keys": [
251+
.init("count"): ["one", "two", "three"],
252+
.init("dom"): ["example", "com"],
253+
.init("dub"): "me/too",
254+
.init("hello"): "Hello World!",
255+
.init("half"): "50%",
256+
.init("var"): "value",
257+
.init("who"): "fred",
258+
.init("base"): "http://example.com/home/",
259+
.init("path"): "/foo/bar",
260+
.init("list"): ["red", "green", "blue"],
261+
.init("keys"): [
262262
"semi": ";",
263263
"dot": ".",
264264
"comma": ",",
265265
],
266-
"v": "6",
267-
"x": "1024",
268-
"y": "768",
269-
"empty": "",
270-
"empty_keys": [:],
266+
.init("v"): "6",
267+
.init("x"): "1024",
268+
.init("y"): "768",
269+
.init("empty"): "",
270+
.init("empty_keys"): [:],
271271
]
272272

273273
Benchmark("URL-Template-expansion") { benchmark in

Proposals/0011-concurrency-safe-notifications.md

Lines changed: 145 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
* Proposal: SF-0011
44
* Author(s): [Philippe Hausler](https://github.com/phausler), [Christopher Thielen](https://github.com/cthielen)
55
* Review Manager: [Charles Hu](https://github.com/iCharlesHu)
6-
* Status: **Accepted**
6+
* Status: **2nd Review** May. 15, 2025 ... May. 22, 2025
77

88
## Revision history
99

1010
* **v1** Initial version
1111
* **v2** Remove `static` from `NotificationCenter.Message.isolation` to better support actor instances
1212
* **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.
1314

1415
## Introduction
1516

@@ -162,7 +163,7 @@ And it could be posted using:
162163
```swift
163164
NotificationCenter.default.post(
164165
NSWorkspace.WillLaunchApplication(application: launchedApplication),
165-
with: workspace
166+
subject: workspace
166167
)
167168
```
168169

@@ -176,7 +177,7 @@ The `NotificationCenter.Message` protocol acts as a base for `NotificationCenter
176177
@available(FoundationPreview 0.5, *)
177178
extension NotificationCenter {
178179
public protocol Message {
179-
associatedtype Subject: AnyObject
180+
associatedtype Subject
180181
static var name: Notification.Name { get }
181182

182183
static func makeMessage(_ notification: Notification) -> Self?
@@ -194,32 +195,51 @@ The protocol specifies `makeMessage(:Notification)` and `makeNotification(:Self)
194195

195196
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.
196197

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+
197200
### Observing messages
198201

199202
Observing messages can be done with new overloads to `addObserver`. Clients do not need to know whether a message conforms to `MainActorMessage` or `AsyncMessage`.
200203

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`.
205+
201206
For `MainActorMessage`:
202207

203208
```swift
204209
@available(FoundationPreview 0.5, *)
205210
extension NotificationCenter {
206211
// e.g. addObserver(of: workspace, for: .willLaunchApplication) { message in ... }
207-
public func addObserver<I: MessageIdentifier, M: MainActorMessage>(of subject: M.Subject,
208-
for identifier: I,
209-
using observer: @escaping @MainActor (M) -> Void)
210-
-> ObservationToken where I.MessageType == M
212+
public func addObserver<Identifier: MessageIdentifier, Message: MainActorMessage>(
213+
of subject: Message.Subject,
214+
for identifier: Identifier,
215+
using observer: @escaping @MainActor (Message) -> Void
216+
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: AnyObject
217+
218+
public func addObserver<Identifier: MessageIdentifier, Message: MainActorMessage>(
219+
of subject: Message.Subject,
220+
for identifier: Identifier,
221+
using observer: @escaping @MainActor (Message) -> Void
222+
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
211223

212224
// e.g. addObserver(of: NSWorkspace.self, for: .willLaunchApplication) { message in ... }
213-
public func addObserver<I: MessageIdentifier, M: MainActorMessage>(of subject: M.Subject.Type,
214-
for identifier: I,
215-
using observer: @escaping @MainActor (M) -> Void)
216-
-> ObservationToken where I.MessageType == M
217-
218-
// e.g. addObserver(NSWorkspace.WillLaunchApplication.self) { message in ... }
219-
public func addObserver<M: MainActorMessage>(_ messageType: M.Type,
220-
subject: M.Subject? = nil,
221-
using observer: @escaping @MainActor (M) -> Void)
222-
-> ObservationToken
225+
public func addObserver<Identifier: MessageIdentifier, Message: MainActorMessage>(
226+
of subject: Message.Subject.Type,
227+
for identifier: Identifier,
228+
using observer: @escaping @MainActor (Message) -> Void
229+
) -> ObservationToken where Identifier.MessageType == Message
230+
231+
// e.g. addObserver(for: NSWorkspace.WillLaunchApplication.self) { message in ... }
232+
public func addObserver<Message: MainActorMessage>(
233+
of subject: Message.Subject? = nil,
234+
for messageType: Message.Type,
235+
using observer: @escaping @MainActor (Message) -> Void
236+
) -> ObservationToken where Message.Subject: AnyObject
237+
238+
public func addObserver<Message: MainActorMessage>(
239+
of subject: Message.Subject? = nil,
240+
for messageType: Message.Type,
241+
using observer: @escaping @MainActor (Message) -> Void
242+
) -> ObservationToken where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
223243
}
224244
```
225245

@@ -228,20 +248,35 @@ And for `AsyncMessage`:
228248
```swift
229249
@available(FoundationPreview 0.5, *)
230250
extension NotificationCenter {
231-
public func addObserver<I: MessageIdentifier, M: AsyncMessage>(of subject: M.Subject,
232-
for identifier: I,
233-
using observer: @escaping @Sendable (M) async -> Void)
234-
-> ObservationToken where I.MessageType == M
235-
236-
public func addObserver<I: MessageIdentifier, M: AsyncMessage>(of subject: M.Subject.Type,
237-
for identifier: I,
238-
using observer: @escaping @Sendable (M) async -> Void)
239-
-> ObservationToken where I.MessageType == M
251+
public func addObserver<Identifier: MessageIdentifier, Message: AsyncMessage>(
252+
of subject: Message.Subject,
253+
for identifier: Identifier,
254+
using observer: @escaping @Sendable (Message) async -> Void
255+
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: AnyObject
256+
257+
public func addObserver<Identifier: MessageIdentifier, Message: AsyncMessage>(
258+
of subject: Message.Subject,
259+
for identifier: Identifier,
260+
using observer: @escaping @Sendable (Message) async -> Void
261+
) -> ObservationToken where Identifier.MessageType == Message, Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
262+
263+
public func addObserver<Identifier: MessageIdentifier, Message: AsyncMessage>(
264+
of subject: Message.Subject.Type,
265+
for identifier: Identifier,
266+
using observer: @escaping @Sendable (Message) async -> Void
267+
) -> ObservationToken where Identifier.MessageType == Message
268+
269+
public func addObserver<Message: AsyncMessage>(
270+
of subject: Message.Subject? = nil,
271+
for messageType: Message.Type,
272+
using observer: @escaping @Sendable (Message) async -> Void
273+
) -> ObservationToken where Message.Subject: AnyObject
240274

241-
public func addObserver<M: AsyncMessage>(_ messageType: M.Type,
242-
subject: M.Subject? = nil,
243-
using observer: @escaping @Sendable (M) async -> Void)
244-
-> ObservationToken
275+
public func addObserver<Message: AsyncMessage>(
276+
of subject: Message.Subject? = nil,
277+
for messageType: Message.Type,
278+
using observer: @escaping @Sendable (Message) async -> Void
279+
) -> ObservationToken where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
245280
}
246281
```
247282

@@ -258,15 +293,91 @@ extension NotificationCenter {
258293
}
259294
```
260295

296+
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.
297+
298+
Messages conforming to `AsyncMessage` can also be observed using a set of `AsyncSequence`-conforming APIs, similar to the existing `notifications(named:object:)` method:
299+
300+
```swift
301+
@available(macOS 16, iOS 19, tvOS 19, watchOS 12, visionOS 3, *)
302+
extension NotificationCenter {
303+
public func messages<Identifier: MessageIdentifier, Message: AsyncMessage>(
304+
of subject: Message.Subject,
305+
for identifier: Identifier,
306+
bufferSize limit: Int = 10
307+
) -> some AsyncSequence<Message, Never> where Identifier.MessageType == Message, Message.Subject: AnyObject
308+
309+
public func messages<Identifier: MessageIdentifier, Message: AsyncMessage>(
310+
of subject: Message.Subject,
311+
for identifier: Identifier,
312+
bufferSize limit: Int = 10
313+
) -> some AsyncSequence<Message, Never> where Identifier.MessageType == Message, Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier {}
314+
315+
public func messages<Identifier: MessageIdentifier, Message: AsyncMessage>(
316+
of subject: Message.Subject.Type,
317+
for identifier: Identifier,
318+
bufferSize limit: Int = 10
319+
) -> some AsyncSequence<Message, Never> where Identifier.MessageType == Message
320+
321+
public func messages<Message: AsyncMessage>(
322+
of subject: Message.Subject? = nil,
323+
for messageType: Message.Type,
324+
bufferSize limit: Int = 10
325+
) -> some AsyncSequence<Message, Never> where Message.Subject: AnyObject
326+
327+
public func messages<Message: AsyncMessage>(
328+
of subject: Message.Subject? = nil,
329+
for messageType: Message.Type,
330+
bufferSize limit: Int = 10
331+
) -> some AsyncSequence<Message, Never> where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
332+
}
333+
```
334+
335+
These allow for the familiar `for await in` syntax:
336+
337+
```swift
338+
for await message in center.messages(of: anObject, for: .anAsyncMessage) {
339+
// ...
340+
}
341+
342+
for await message in center.messages(for: AnAsyncMessage.self) {
343+
// ...
344+
}
345+
346+
// etc.
347+
```
348+
349+
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()`.
350+
261351
### Posting messages
262352

263353
Posting messages can be done with new overloads on the existing `post` method:
264354

265355
```swift
266356
@available(FoundationPreview 0.5, *)
267357
extension NotificationCenter {
268-
public func post<M: Message>(_ message: M, with subject: M.Subject)
269-
public func post<M: Message>(_ message: M, with subject: M.Subject.Type)
358+
359+
// MainActorMessage post()
360+
361+
@MainActor
362+
public func post<Message: MainActorMessage>(_ message: Message, subject: Message.Subject)
363+
where Message.Subject: AnyObject
364+
365+
@MainActor
366+
public func post<Message: MainActorMessage>(_ message: Message, subject: Message.Subject)
367+
where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
368+
369+
@MainActor
370+
public func post<Message: MainActorMessage>(_ message: Message, subject: Message.Subject.Type = Message.Subject.self)
371+
372+
// AsyncMessage post()
373+
374+
public func post<Message: AsyncMessage>(_ message: Message, subject: Message.Subject)
375+
where Message.Subject: AnyObject
376+
377+
public func post<Message: AsyncMessage>(_ message: Message, subject: Message.Subject)
378+
where Message.Subject: Identifiable, Message.Subject.ID == ObjectIdentifier
379+
380+
public func post<Message: AsyncMessage>(_ message: Message, subject: Message.Subject.Type = Message.Subject.self)
270381
}
271382
```
272383

@@ -289,7 +400,7 @@ struct EventDidOccur: NotificationCenter.Message {
289400
}
290401

291402
static func makeNotification(_ message: Self) -> Notification {
292-
return Notification(name: Self.name, userInfo: ["foo": self.foo])
403+
return Notification(name: Self.name, object: object, userInfo: ["foo": self.foo])
293404
}
294405
}
295406
```
@@ -376,7 +487,7 @@ We could alternatively ferry `subject` in both the observer closure and `post()`
376487
center.addObserver(of: someSubject, for: .someMessage) { message, subject in ... }
377488

378489
// Nor post() ...
379-
center.post(SomeMessage(), with: someSubject)
490+
center.post(SomeMessage(), subject: someSubject)
380491
```
381492

382493
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

Comments
 (0)