Skip to content

Commit 9ef1643

Browse files
authored
Add NotificationCenter dependency (#385)
* Add `NotificationCenter` dependency With XCTest we recommended using the default center since tests were pretty much quarantined to their own processes, but with Swift Testing, that's changed, so controlling this dependency is worth a new API. * Modernize CI * wip
1 parent 8604a33 commit 9ef1643

File tree

5 files changed

+110
-24
lines changed

5 files changed

+110
-24
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,10 @@ jobs:
3434
- name: Build for library evolution
3535
run: make build-for-library-evolution
3636

37-
macos-14:
38-
name: macOS 14
39-
runs-on: macos-14
40-
strategy:
41-
matrix:
42-
config: ['debug', 'release']
43-
xcode: ['15.4']
44-
steps:
45-
- uses: actions/checkout@v4
46-
- name: Select Xcode ${{ matrix.xcode }}
47-
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
48-
- name: Skip macro validation
49-
run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES
50-
- name: Run tests
51-
run: make test-swift
52-
- name: Build platforms ${{ matrix.config }}
53-
run: CONFIG=${{ matrix.config }} make build-all-platforms
54-
- name: Build for library evolution
55-
run: make build-for-library-evolution
56-
5737
ubuntu:
5838
strategy:
5939
matrix:
6040
swift:
61-
- '5.10'
6241
- '6.0'
6342
name: Ubuntu (Swift ${{ matrix.swift }})
6443
runs-on: ubuntu-latest

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ test-linux:
3131
--rm \
3232
-v "$(PWD):$(PWD)" \
3333
-w "$(PWD)" \
34-
swift:5.10-focal \
34+
swift:6.1-focal \
3535
bash -c 'apt-get update && apt-get -y install make && make test-swift'
3636

3737
build-for-static-stdlib:
@@ -61,12 +61,12 @@ build-for-static-stdlib-docker:
6161
@docker run \
6262
-v "$(PWD):$(PWD)" \
6363
-w "$(PWD)" \
64-
swift:5.9-focal \
64+
swift:6.1-focal \
6565
bash -c "swift build -c debug --static-swift-stdlib"
6666
@docker run \
6767
-v "$(PWD):$(PWD)" \
6868
-w "$(PWD)" \
69-
swift:5.9-focal \
69+
swift:6.1-focal \
7070
bash -c "swift build -c release --static-swift-stdlib"
7171

7272
format:
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#if canImport(Foundation)
2+
import Foundation
3+
4+
extension DependencyValues {
5+
/// The notification center that features should use.
6+
///
7+
/// By default, `NotificationCenter.default` is provided. When used in tests, a task-local
8+
/// center is provided, instead.
9+
///
10+
/// You can access notification center from a feature by introducing a ``Dependency`` property
11+
/// wrapper to the property:
12+
///
13+
/// ```swift
14+
/// @Observable
15+
/// final class FeatureModel {
16+
/// @ObservationIgnored
17+
/// @Dependency(\.notificationCenter) var notificationCenter
18+
/// // ...
19+
/// }
20+
/// ```
21+
public var notificationCenter: NotificationCenter {
22+
get { self[NotificationCenterKey.self] }
23+
set { self[NotificationCenterKey.self] = newValue }
24+
}
25+
26+
private enum NotificationCenterKey: DependencyKey {
27+
static let liveValue = NotificationCenter.default
28+
static var testValue: NotificationCenter { NotificationCenter() }
29+
}
30+
}
31+
#endif

Sources/Dependencies/Documentation.docc/Extensions/DependencyValues.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- ``locale``
3131
- ``mainQueue``
3232
- ``mainRunLoop``
33+
- ``notificationCenter``
3334
- ``openURL``
3435
- ``precondition``
3536
- ``suspendingClock``
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#if canImport(Combine)
2+
import Combine
3+
import Dependencies
4+
import Foundation
5+
import Testing
6+
7+
func post() {
8+
@Dependency(\.notificationCenter) var notificationCenter
9+
notificationCenter.post(Notification(name: notificationName))
10+
}
11+
12+
private let notificationName = Notification.Name("Hello")
13+
14+
struct NotificationCenterTests {
15+
@Dependency(\.notificationCenter) var notificationCenter
16+
var cancellables: Set<AnyCancellable> = []
17+
18+
@Test mutating func basics() {
19+
var defaultReceived = 0
20+
var dependencyReceived = 0
21+
NotificationCenter.default
22+
.publisher(for: notificationName).sink { _ in defaultReceived += 1 }
23+
.store(in: &cancellables)
24+
notificationCenter
25+
.publisher(for: notificationName).sink { _ in dependencyReceived += 1 }
26+
.store(in: &cancellables)
27+
post()
28+
#expect(defaultReceived == 0)
29+
#expect(dependencyReceived == 1)
30+
}
31+
32+
@Test mutating func concurrent1() async throws {
33+
nonisolated(unsafe) var count = 0
34+
notificationCenter
35+
.publisher(for: notificationName)
36+
.sink { _ in count += 1 }
37+
.store(in: &cancellables)
38+
39+
for _ in 1...100 {
40+
notificationCenter.post(name: notificationName, object: nil)
41+
try await Task.sleep(for: .milliseconds(1))
42+
}
43+
#expect(count == 100)
44+
}
45+
46+
@Test mutating func concurrent2() async throws {
47+
nonisolated(unsafe) var count = 0
48+
notificationCenter
49+
.publisher(for: notificationName)
50+
.sink { _ in count += 1 }
51+
.store(in: &cancellables)
52+
53+
for _ in 1...100 {
54+
notificationCenter.post(name: notificationName, object: nil)
55+
try await Task.sleep(for: .milliseconds(1))
56+
}
57+
#expect(count == 100)
58+
}
59+
60+
@Test
61+
mutating func concurrent3() async throws {
62+
nonisolated(unsafe) var count = 0
63+
notificationCenter
64+
.publisher(for: notificationName)
65+
.sink { _ in count += 1 }
66+
.store(in: &cancellables)
67+
68+
for _ in 1...100 {
69+
notificationCenter.post(name: notificationName, object: nil)
70+
try await Task.sleep(for: .milliseconds(1))
71+
}
72+
#expect(count == 100)
73+
}
74+
}
75+
#endif

0 commit comments

Comments
 (0)