mirror of
https://github.com/samsonjs/NotificationSmuggler.git
synced 2026-03-25 08:25:48 +00:00
Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a045a9386 | |||
| 17a78eb8d0 | |||
| 94845eb55c | |||
| f7c096ae62 | |||
| 1257fa1771 | |||
| 488a77205c | |||
| 7057d23b72 | |||
| 5564fcba93 | |||
| 832380d272 | |||
| ab10829953 |
7 changed files with 421 additions and 28 deletions
58
Changelog.md
Normal file
58
Changelog.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Your change here.
|
||||
|
||||
[Unreleased]: https://github.com/samsonjs/NotificationSmuggler/compare/0.2.1...HEAD
|
||||
|
||||
## [0.2.1] - 2025-06-06
|
||||
|
||||
[Compare with 0.2.0](https://github.com/samsonjs/NotificationSmuggler/compare/0.2.0...0.2.1)
|
||||
|
||||
### Added
|
||||
- Support for optional object parameter in notification posting
|
||||
- Comprehensive DocC documentation with examples and best practices
|
||||
- Enhanced API documentation with usage examples
|
||||
|
||||
### Changed
|
||||
- Improved documentation throughout the codebase
|
||||
- Enhanced test coverage for new functionality
|
||||
|
||||
## [0.2.0] - 2025-06-06
|
||||
|
||||
[Compare with 0.1.2](https://github.com/samsonjs/NotificationSmuggler/compare/0.1.2...0.2.0)
|
||||
|
||||
### Added
|
||||
- [#1](https://github.com/samsonjs/NotificationSmuggler/pull/1): `NotificationCenter.smuggle` extension method for improved ergonomics - [@samsonjs](https://github.com/samsonjs).
|
||||
- Better API for posting notifications directly from NotificationCenter
|
||||
|
||||
### Changed
|
||||
- Improved logging using `os.log` instead of `NSLog`
|
||||
- Enhanced overall package documentation
|
||||
|
||||
## [0.1.2] - 2025-04-29
|
||||
|
||||
[Compare with 0.1.1](https://github.com/samsonjs/NotificationSmuggler/compare/0.1.1...0.1.2)
|
||||
|
||||
### Changed
|
||||
- Updated documentation and version references
|
||||
|
||||
## [0.1.1] - 2025-04-29
|
||||
|
||||
[Compare with 0.1.0](https://github.com/samsonjs/NotificationSmuggler/compare/0.1.0...0.1.1)
|
||||
|
||||
### Changed
|
||||
- Fixed deployment targets for iOS 18.0+ and macOS 15.0+
|
||||
- Updated README with comprehensive usage examples and documentation
|
||||
|
||||
## [0.1.0] - 2025-04-29
|
||||
|
||||
### Added
|
||||
- Initial release of NotificationSmuggler
|
||||
- `Smuggled` protocol for type-safe notification handling
|
||||
- `Notification` and `NotificationCenter` extensions for smuggling notifications
|
||||
- Support for async/await and Combine notification observation
|
||||
- Swift 6 concurrency support with `Sendable` conformance
|
||||
- Comprehensive test suite using Swift Testing framework
|
||||
- iOS 18.0+ and macOS 15.0+ platform support
|
||||
|
|
@ -6,8 +6,8 @@ import PackageDescription
|
|||
let package = Package(
|
||||
name: "NotificationSmuggler",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v12),
|
||||
.iOS(.v18),
|
||||
.macOS(.v15),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||
|
|
|
|||
16
Readme.md
16
Readme.md
|
|
@ -8,10 +8,12 @@
|
|||
|
||||
NotificationSmuggler is a tiny Swift package that makes it easy to embed strongly-typed values in `Notification`s, and extract them out on the receiving end as well. Nothing elaborate, it sneaks the contraband in the `userInfo` dictionary.
|
||||
|
||||
Declare a type conforming to `Smuggled` and then use the static method `Notification.smuggle(_:object:)` when posting the notification. On the receiving side of things you can use the extension methods `NotificationCenter.notifications(for:)` and `NotificationCenter.publisher(for:)` to observe the strongly-typed values without manually mapping them yourself.
|
||||
Declare a type conforming to `Smuggled` and then use the static method `Notification.smuggle(_:object:)` when posting the notification. On the receiving side of things you can use the extension methods `NotificationCenter.notifications(for:object:)` and `NotificationCenter.publisher(for:object:)` to observe the strongly-typed values without manually mapping them yourself.
|
||||
|
||||
If you have `Sendable` contraband then all of this will work nicely with Swift 6 and complete concurrency checking.
|
||||
|
||||
This package pairs nicely with [AsyncMonitor](https://github.com/samsonjs/AsyncMonitor) for a complete notification handling system in the Swift 6 concurrency world.
|
||||
|
||||
## Usage
|
||||
|
||||
### Define a smuggled payload
|
||||
|
|
@ -28,11 +30,17 @@ The `Smuggled` protocol provides static `notificationName` and `userInfoKey` pro
|
|||
|
||||
### Post a notification
|
||||
|
||||
```swift
|
||||
NotificationCenter.default.smuggle(SomeNotification(answer: 42))
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```swift
|
||||
NotificationCenter.default.post(.smuggle(SomeNotification(answer: 42)))
|
||||
```
|
||||
|
||||
This automatically sets the `.name`, `userInfo`, and optionally `.object` for the notification.
|
||||
Both automatically set the `.name`, `userInfo`, and optionally `.object` for the notification.
|
||||
|
||||
### Observe and extract contraband
|
||||
|
||||
|
|
@ -53,7 +61,7 @@ The only way to install this package is with Swift Package Manager (SPM). Please
|
|||
|
||||
### Supported Platforms
|
||||
|
||||
This package is supported on iOS 16.0+ and macOS 12.0+.
|
||||
This package is supported on iOS 18.0+ and macOS 15.0+.
|
||||
|
||||
### Xcode
|
||||
|
||||
|
|
@ -64,7 +72,7 @@ When you're integrating this into an app with Xcode then go to your project's Pa
|
|||
When you're integrating this using SPM on its own then add this to the list of dependencies your Package.swift file:
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/samsonjs/NotificationSmuggler.git", .upToNextMajor(from: "0.1.0"))
|
||||
.package(url: "https://github.com/samsonjs/NotificationSmuggler.git", .upToNextMajor(from: "0.2.1"))
|
||||
```
|
||||
|
||||
and then add `"NotificationSmuggler"` to the list of dependencies in your target as well.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
# NotificationSmuggler
|
||||
|
||||
A Swift 6 package for type-safe notification handling with strongly-typed values with strict concurrency, using async/await or Combine publishers.
|
||||
|
||||
Never touch a `userInfo` dictionary again for your own notifications.
|
||||
|
||||
## Overview
|
||||
|
||||
NotificationSmuggler makes it easy to embed strongly-typed values in `Notification`s and extract them on the receiving end. It "smuggles" your contraband payload through the `userInfo` dictionary while providing a clean, type-safe API.
|
||||
|
||||
Each conforming type gets its own unique notification name and userInfo key, automatically generated from the type name. There are convenience methods for posting and observing your contraband. Structs are recommended because you may want these to be `Sendable`, and inheritance isn't supported so hierarchies may pose pitfalls if you try to get fancy.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Define your contraband
|
||||
|
||||
Create a type that conforms to the ``Smuggled`` protocol:
|
||||
|
||||
```swift
|
||||
struct AccountAuthenticated: Smuggled, Sendable {
|
||||
let accountID: String
|
||||
let timestamp: Date
|
||||
}
|
||||
```
|
||||
|
||||
### Post Notifications
|
||||
|
||||
Use the convenience method to post directly:
|
||||
|
||||
```swift
|
||||
NotificationCenter.default.smuggle(AccountAuthenticatedNotification(
|
||||
accountID: "abc123",
|
||||
timestamp: .now
|
||||
))
|
||||
```
|
||||
|
||||
Or create a notification first if that's more convenient:
|
||||
|
||||
```swift
|
||||
let notification = Notification.smuggle(AccountAuthenticatedNotification(
|
||||
accountID: "abc123",
|
||||
timestamp: .now
|
||||
))
|
||||
NotificationCenter.default.post(notification)
|
||||
```
|
||||
|
||||
### Observe Notifications
|
||||
|
||||
With async/await (recommended for Sendable types):
|
||||
|
||||
```swift
|
||||
Task {
|
||||
for await authenticated in NotificationCenter.default.notifications(for: AccountAuthenticatedNotification.self) {
|
||||
print("Account \(authenticated.accountID) authenticated at \(authenticated.timestamp)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With Combine:
|
||||
|
||||
```swift
|
||||
NotificationCenter.default.publisher(for: AccountAuthenticatedNotification.self)
|
||||
.sink { authenticated in
|
||||
print("Account \(authenticated.accountID) authenticated")
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
|
||||
### Object-Specific Observation
|
||||
|
||||
Filter notifications by sender object:
|
||||
|
||||
```swift
|
||||
let document = AwesomeDocument()
|
||||
|
||||
// Only receive notifications from this specific document
|
||||
for await saved in NotificationCenter.default.notifications(for: DocumentSavedNotification.self, object: document) {
|
||||
print("Document saved: \(saved.filename)")
|
||||
}
|
||||
```
|
||||
|
||||
## Topics
|
||||
|
||||
### Essential Types
|
||||
|
||||
- ``Smuggled``
|
||||
|
||||
### Notification Creation
|
||||
|
||||
- ``Foundation/Notification/smuggle(_:object:)``
|
||||
- ``Foundation/NotificationCenter/smuggle(_:object:)``
|
||||
|
||||
### Notification Observation
|
||||
|
||||
- ``Foundation/NotificationCenter/notifications(for:object:)``
|
||||
- ``Foundation/NotificationCenter/publisher(for:object:)``
|
||||
|
||||
### Extracting Values
|
||||
|
||||
- ``Foundation/Notification/smuggled()``
|
||||
|
|
@ -1,14 +1,51 @@
|
|||
import Foundation
|
||||
|
||||
/// A marker protocol for types that represent notifications with associated data. Conforming types gain a default notification name and
|
||||
/// user info key to facilitate smuggling. They can be used with extension methods like
|
||||
/// ``NotificationCenter.notifications(for:)`` and ``NotificationCenter.publisher(for:)`` which
|
||||
/// automatically extract and cast this type from user info.
|
||||
/// A marker protocol for types that represent notifications with associated data.
|
||||
///
|
||||
/// If you want to extract the contraband manually you can use the extension method ``Notification.smuggled()``.
|
||||
/// Conforming types automatically gain a default notification name and user-info key to facilitate
|
||||
/// type-safe notification handling. The protocol enables strongly-typed notification posting and
|
||||
/// observation without manual `userInfo` dictionary manipulation.
|
||||
///
|
||||
/// When smuggling notifications you can use ``Notification.smuggle(:object:)`` to build up a notification with the correct
|
||||
/// user info automatically.
|
||||
/// ## Basic Usage
|
||||
///
|
||||
/// Define a notification type:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct AccountAuthenticatedNotification: Smuggled, Sendable {
|
||||
/// let accountID: String
|
||||
/// let timestamp: Date
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Post the notification:
|
||||
///
|
||||
/// ```swift
|
||||
/// NotificationCenter.default.smuggle(AccountAuthenticatedNotification(
|
||||
/// accountID: "abc123",
|
||||
/// timestamp: .now
|
||||
/// ))
|
||||
/// ```
|
||||
///
|
||||
/// Observe notifications:
|
||||
///
|
||||
/// ```swift
|
||||
/// for await authentication in NotificationCenter.default.notifications(for: AccountAuthenticatedNotification.self) {
|
||||
/// print("Account \(authentication.accountID) authenticated")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Sendable Considerations
|
||||
///
|
||||
/// For Swift 6 concurrency, consider making your notification types `Sendable` when they might
|
||||
/// cross actor boundaries. Value types with `Sendable` properties are automatically `Sendable`.
|
||||
///
|
||||
/// ## Available Methods
|
||||
///
|
||||
/// - ``NotificationCenter.smuggle(_:object:)`` - Post notification directly
|
||||
/// - ``Notification.smuggle(_:object:)`` - Create notification instance
|
||||
/// - ``NotificationCenter.notifications(for:object:)`` - Async observation
|
||||
/// - ``NotificationCenter.publisher(for:object:)`` - Combine observation
|
||||
/// - ``Notification.smuggled()`` - Manual extraction
|
||||
public protocol Smuggled {}
|
||||
|
||||
public extension Smuggled {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,35 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
private let log = Logger(subsystem: "NotificationSmuggler", category: "smuggling")
|
||||
|
||||
public extension Notification {
|
||||
/// Creates a `Notification` instance that smuggles the given `Smuggled` value.
|
||||
///
|
||||
/// This method automatically configures the notification's `name` using the type's
|
||||
/// ``Smuggled/notificationName`` and embeds the value in `userInfo` using the type's
|
||||
/// ``Smuggled/userInfoKey``.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```swift
|
||||
/// struct CoolEvent: Smuggled {
|
||||
/// let message: String
|
||||
/// }
|
||||
///
|
||||
/// let notification = Notification.smuggle(CoolEvent(message: "Hello"))
|
||||
/// NotificationCenter.default.post(notification)
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - contraband: The `Smuggled` value to send.
|
||||
/// - object: An optional sender object.
|
||||
/// - contraband: The `Smuggled` value to embed in the notification.
|
||||
/// - object: An optional sender object to associate with the notification.
|
||||
/// - Returns: A configured `Notification` with `name`, `object`, and `userInfo`.
|
||||
///
|
||||
/// ## See Also
|
||||
///
|
||||
/// - ``NotificationCenter.smuggle(_:object:)``
|
||||
static func smuggle<Contraband: Smuggled>(
|
||||
_ contraband: Contraband,
|
||||
object: Any? = nil
|
||||
|
|
@ -21,14 +43,36 @@ public extension Notification {
|
|||
|
||||
/// Extracts a `Smuggled` value (contraband) of the specified type from this `Notification`'s `userInfo`.
|
||||
///
|
||||
/// - Returns: The extracted `Smuggled` value when found, otherwise `nil`.
|
||||
/// This method performs type-safe extraction by looking for the value using the type's
|
||||
/// ``Smuggled/userInfoKey`` and attempting to cast it to the expected type.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```swift
|
||||
/// func handleNotification(_ notification: Notification) {
|
||||
/// if let event: MyEvent = notification.smuggled() {
|
||||
/// print("Received: \(event.message)")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Error Handling
|
||||
///
|
||||
/// - Missing values and type-casting failures are logged at `error` level since that means you tried something fancy and messed it up. No offence, them's the facts.
|
||||
///
|
||||
/// - Returns: The extracted `Smuggled` value when found and properly typed, otherwise `nil`.
|
||||
///
|
||||
/// ## See Also
|
||||
///
|
||||
/// - ``NotificationCenter.notifications(for:object:)``
|
||||
/// - ``NotificationCenter.publisher(for:object:)``
|
||||
func smuggled<Contraband: Smuggled>() -> Contraband? {
|
||||
guard let instance = userInfo?[Contraband.userInfoKey] else {
|
||||
NSLog("[\(Contraband.self)] Value not found in userInfo[\"\(Contraband.userInfoKey)\"]")
|
||||
log.error("Value not found in userInfo[\"\(Contraband.userInfoKey)\"] for \(Contraband.notificationName.rawValue)")
|
||||
return nil
|
||||
}
|
||||
guard let contraband = instance as? Contraband else {
|
||||
NSLog("[\(Contraband.self)] Failed to cast \(instance) as \(Contraband.self)")
|
||||
log.error("Failed to cast \(String(describing: instance)) as \(Contraband.notificationName.rawValue)")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -39,27 +83,105 @@ public extension Notification {
|
|||
// MARK: -
|
||||
|
||||
public extension NotificationCenter {
|
||||
/// Posts a notification that smuggles the given `Smuggled` value.
|
||||
///
|
||||
/// This is a convenience method that combines ``Notification.smuggle(_:object:)``
|
||||
/// and `post(_:)` into a single operation.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```swift
|
||||
/// struct AccountAuthenticated: Smuggled {
|
||||
/// let accountID: String
|
||||
/// }
|
||||
///
|
||||
/// // Instead of:
|
||||
/// // NotificationCenter.default.post(.smuggle(AccountAuthenticated(accountID: "abc123")))
|
||||
///
|
||||
/// // You can write:
|
||||
/// NotificationCenter.default.smuggle(AccountAuthenticated(accountID: "abc123"))
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - contraband: The `Smuggled` value to send.
|
||||
/// - object: An optional sender object to associate with the notification.
|
||||
///
|
||||
/// ## See Also
|
||||
///
|
||||
/// - ``Notification.smuggle(_:object:)``
|
||||
func smuggle<Contraband: Smuggled>(_ contraband: Contraband, object: Any? = nil) {
|
||||
post(.smuggle(contraband, object: object))
|
||||
}
|
||||
|
||||
/// Returns an `AsyncSequence` of notifications of a specific `Smuggled` type.
|
||||
///
|
||||
/// Each element of the sequence is a `Smuggled` value.
|
||||
/// This method provides async/await-compatible observation of notifications. It automatically
|
||||
/// filters for the specified type and extracts the values from `userInfo`, yielding only
|
||||
/// successfully extracted values.
|
||||
///
|
||||
/// - Parameter contrabandType: The `Smuggled` type to observe..
|
||||
/// - Returns: An `AsyncSequence` of `NotificationSmuggler` values.
|
||||
/// ## Example
|
||||
///
|
||||
/// ```swift
|
||||
/// struct NetworkStatusChanged: Smuggled, Sendable {
|
||||
/// let isOnline: Bool
|
||||
/// }
|
||||
///
|
||||
/// Task {
|
||||
/// for await status in NotificationCenter.default.notifications(for: NetworkStatusChanged.self) {
|
||||
/// print("Network is \(status.isOnline ? "online" : "offline")")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Parameter contrabandType: The `Smuggled` type to observe.
|
||||
/// - Returns: An `AsyncSequence` that yields extracted notification values.
|
||||
///
|
||||
/// ## See Also
|
||||
///
|
||||
/// - ``publisher(for:object:)``
|
||||
func notifications<Contraband: Smuggled>(
|
||||
for contrabandType: Contraband.Type
|
||||
for contrabandType: Contraband.Type,
|
||||
object: (AnyObject & Sendable)? = nil
|
||||
) -> any AsyncSequence<Contraband, Never> {
|
||||
notifications(named: Contraband.notificationName)
|
||||
notifications(named: Contraband.notificationName, object: object)
|
||||
.compactMap { $0.smuggled() }
|
||||
}
|
||||
|
||||
/// Returns a Combine publisher that emits `Smuggled` values of the given type.
|
||||
///
|
||||
/// - Parameter contrabandType: The `Smuggled` type to observe.
|
||||
/// This method provides Combine-compatible observation of notifications from a specific sender.
|
||||
/// It automatically filters for the specified type and object, extracting values from `userInfo`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```swift
|
||||
/// struct DocumentSavedNotification: Smuggled {
|
||||
/// let filename: String
|
||||
/// }
|
||||
///
|
||||
/// let document = AwesomeDocument()
|
||||
/// var cancellables = Set<AnyCancellable>()
|
||||
///
|
||||
/// NotificationCenter.default.publisher(for: DocumentSavedNotification.self, object: document)
|
||||
/// .sink { saved in
|
||||
/// print("Document \(saved.filename) was saved")
|
||||
/// }
|
||||
/// .store(in: &cancellables)
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - contrabandType: The `Smuggled` type to observe.
|
||||
/// - object: The optional object whose notifications you want to receive. Must be a class instance.
|
||||
/// - Returns: A publisher emitting `Smuggled` values.
|
||||
///
|
||||
/// ## See Also
|
||||
///
|
||||
/// - ``notifications(for:object:)``
|
||||
func publisher<Contraband: Smuggled>(
|
||||
for contrabandType: Contraband.Type
|
||||
for contrabandType: Contraband.Type,
|
||||
object: AnyObject? = nil
|
||||
) -> AnyPublisher<Contraband, Never> {
|
||||
publisher(for: contrabandType.notificationName)
|
||||
publisher(for: contrabandType.notificationName, object: object)
|
||||
.compactMap { $0.smuggled() }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,22 @@ struct SmugglingTests {
|
|||
|
||||
// MARK: NotificationCenter extensions
|
||||
|
||||
@Test(.timeLimit(.minutes(1)))
|
||||
@MainActor func notificationCenterSmuggle() async {
|
||||
let center = NotificationCenter()
|
||||
let task = Task {
|
||||
for await contraband in center.notifications(for: HitchhikersNotification.self) {
|
||||
#expect(contraband.answer == 42)
|
||||
return
|
||||
}
|
||||
}
|
||||
await Task.yield()
|
||||
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
center.smuggle(contraband)
|
||||
await task.value
|
||||
}
|
||||
|
||||
// It's important that the tests and the notification-observing tasks are not on the same actor,
|
||||
// so we make the tests @MainActor and observe notifications on another actor. Otherwise it's
|
||||
// a deadlock.
|
||||
|
|
@ -60,7 +76,7 @@ struct SmugglingTests {
|
|||
await Task.yield()
|
||||
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
center.post(.smuggle(contraband))
|
||||
center.smuggle(contraband)
|
||||
while !received { await Task.yield() }
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +117,7 @@ struct SmugglingTests {
|
|||
await Task.yield()
|
||||
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
center.post(.smuggle(contraband))
|
||||
center.smuggle(contraband)
|
||||
while !received { await Task.yield() }
|
||||
}
|
||||
|
||||
|
|
@ -128,4 +144,56 @@ struct SmugglingTests {
|
|||
try await Task.sleep(for: .milliseconds(10))
|
||||
#expect(!received)
|
||||
}
|
||||
|
||||
// MARK: Object-specific notifications
|
||||
|
||||
final class SenderObject: NSObject, Sendable {}
|
||||
|
||||
@Test(.timeLimit(.minutes(1)))
|
||||
@MainActor func notificationCenterAsyncSequenceWithObject() async throws {
|
||||
let center = NotificationCenter()
|
||||
let senderObject = SenderObject()
|
||||
let decoyObject = NSObject()
|
||||
|
||||
let task = Task {
|
||||
for await contraband in center.notifications(for: HitchhikersNotification.self, object: senderObject) {
|
||||
#expect(contraband.answer == 42)
|
||||
return
|
||||
}
|
||||
}
|
||||
await Task.yield()
|
||||
|
||||
// Post from decay object, should be ignored
|
||||
center.post(.smuggle(HitchhikersNotification(answer: 99), object: decoyObject))
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
|
||||
// Post from sender object, should be received
|
||||
center.post(.smuggle(HitchhikersNotification(answer: 42), object: senderObject))
|
||||
await task.value
|
||||
}
|
||||
|
||||
@Test(.timeLimit(.minutes(1)))
|
||||
@MainActor func notificationCenterPublisherWithObject() async throws {
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
let center = NotificationCenter()
|
||||
let senderObject = SenderObject()
|
||||
let decoyObject = NSObject()
|
||||
nonisolated(unsafe) var received = false
|
||||
|
||||
center.publisher(for: HitchhikersNotification.self, object: senderObject)
|
||||
.sink { contraband in
|
||||
#expect(contraband.answer == 42)
|
||||
received = true
|
||||
}.store(in: &cancellables)
|
||||
await Task.yield()
|
||||
|
||||
// Post from decoy object, should be ignored
|
||||
center.post(.smuggle(HitchhikersNotification(answer: 99), object: decoyObject))
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
#expect(!received)
|
||||
|
||||
// Post from sender object, should be received
|
||||
center.post(.smuggle(HitchhikersNotification(answer: 42), object: senderObject))
|
||||
while !received { await Task.yield() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue