mirror of
https://github.com/samsonjs/NotificationSmuggler.git
synced 2026-03-28 08:55:47 +00:00
Rename to NotificationSmuggler
This commit is contained in:
parent
59effef18f
commit
a43dd347dd
9 changed files with 148 additions and 143 deletions
|
|
@ -4,7 +4,7 @@
|
|||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "BetterNotification",
|
||||
name: "NotificationSmuggler",
|
||||
platforms: [
|
||||
.iOS(.v18),
|
||||
.macOS(.v15),
|
||||
|
|
@ -12,17 +12,17 @@ let package = Package(
|
|||
products: [
|
||||
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||
.library(
|
||||
name: "BetterNotification",
|
||||
targets: ["BetterNotification"]),
|
||||
name: "NotificationSmuggler",
|
||||
targets: ["NotificationSmuggler"]),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||
// Targets can depend on other targets in this package and products from dependencies.
|
||||
.target(
|
||||
name: "BetterNotification"),
|
||||
name: "NotificationSmuggler"),
|
||||
.testTarget(
|
||||
name: "BetterNotificationTests",
|
||||
dependencies: ["BetterNotification"]
|
||||
name: "NotificationSmugglerTests",
|
||||
dependencies: ["NotificationSmuggler"]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
|
|||
17
Readme.md
17
Readme.md
|
|
@ -1,8 +1,8 @@
|
|||
# BetterNotification
|
||||
# NotificationSmuggler
|
||||
|
||||
[](https://0dependencies.dev)
|
||||
[](https://swiftpackageindex.com/samsonjs/BetterNotification)
|
||||
[](https://swiftpackageindex.com/samsonjs/BetterNotification)
|
||||
[](https://swiftpackageindex.com/samsonjs/NotificationSmuggler)
|
||||
[](https://swiftpackageindex.com/samsonjs/NotificationSmuggler)
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
@ -14,12 +14,9 @@ tktk
|
|||
|
||||
## Installation
|
||||
|
||||
Honestly you should probably just copy [BetterNotification.swift]() into your project.
|
||||
|
||||
The only way to install this package is with Swift Package Manager (SPM). Please [file a new issue][] or submit a pull-request if you want to use something else.
|
||||
|
||||
[BetterNotification.swift]: https://github.com/samsonjs/BetterNotification/blob/main/Sources/BetterNotification/BetterNotification.swift
|
||||
[file a new issue]: https://github.com/samsonjs/BetterNotification/issues/new
|
||||
[file a new issue]: https://github.com/samsonjs/NotificationSmuggler/issues/new
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
|
|
@ -27,17 +24,17 @@ This package is supported on iOS 16.0+ and macOS 12.0+.
|
|||
|
||||
### Xcode
|
||||
|
||||
When you're integrating this into an app with Xcode then go to your project's Package Dependencies and enter the URL `https://github.com/samsonjs/BetterNotification` and then go through the usual flow for adding packages.
|
||||
When you're integrating this into an app with Xcode then go to your project's Package Dependencies and enter the URL `https://github.com/samsonjs/NotificationSmuggler` and then go through the usual flow for adding packages.
|
||||
|
||||
### Swift Package Manager (SPM)
|
||||
|
||||
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/BetterNotification.git", .upToNextMajor(from: "0.1.0"))
|
||||
.package(url: "https://github.com/samsonjs/NotificationSmuggler.git", .upToNextMajor(from: "0.1.0"))
|
||||
```
|
||||
|
||||
and then add `"BetterNotification"` to the list of dependencies in your target as well.
|
||||
and then add `"NotificationSmuggler"` to the list of dependencies in your target as well.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
|
||||
/// A marker protocol for types that represent notifications with associated data. Conforming types gain a default notification name and
|
||||
/// user info key. 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.
|
||||
///
|
||||
/// When posting better notifications you can use ``Notification.better(:object:)`` to build up a notification with the correct
|
||||
/// user info automatically.
|
||||
public protocol BetterNotification {}
|
||||
|
||||
public extension BetterNotification {
|
||||
/// The notification name associated with the conforming type.
|
||||
///
|
||||
/// Uses the type's name to create a unique raw value in the format:
|
||||
/// `"BetterNotification:{SelfType}"`.
|
||||
static var notificationName: Notification.Name {
|
||||
Notification.Name(rawValue: "BetterNotification:\(Self.self)")
|
||||
}
|
||||
|
||||
/// The key used in the notification's `userInfo` dictionary to store the notification value.
|
||||
///
|
||||
/// Matches the raw value of `notificationName`.
|
||||
static var userInfoKey: String { notificationName.rawValue }
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public extension Notification {
|
||||
/// Creates a `Notification` instance for the given `BetterNotification` value.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - betterNotification: The `BetterNotification` value to send.
|
||||
/// - object: An optional sender object.
|
||||
/// - Returns: A configured `Notification` with `name`, `object`, and `userInfo`.
|
||||
static func better<BN: BetterNotification>(
|
||||
_ betterNotification: BN,
|
||||
object: Any? = nil
|
||||
) -> Notification {
|
||||
Notification(
|
||||
name: BN.notificationName,
|
||||
object: object,
|
||||
userInfo: [BN.userInfoKey: betterNotification]
|
||||
)
|
||||
}
|
||||
|
||||
/// Extracts a `BetterNotification` value of the specified type from this `Notification`'s `userInfo`.
|
||||
///
|
||||
/// - Returns: The extracted `BetterNotification` value when found, otherwise `nil`.
|
||||
func better<BN: BetterNotification>() -> BN? {
|
||||
guard let object = userInfo?[BN.userInfoKey] else {
|
||||
NSLog("[\(BN.self)] Object missing from userInfo[\"\(BN.userInfoKey)\"]")
|
||||
return nil
|
||||
}
|
||||
guard let betterNotification = object as? BN else {
|
||||
NSLog("[\(BN.self)] Failed to cast \(object) to \(BN.self)")
|
||||
return nil
|
||||
}
|
||||
|
||||
return betterNotification
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public extension NotificationCenter {
|
||||
/// Returns an `AsyncSequence` of notifications of a specific `BetterNotification` type.
|
||||
///
|
||||
/// Each element of the sequence is a `BetterNotification` value.
|
||||
///
|
||||
/// - Parameter betterType: The `BetterNotification` type to observe..
|
||||
/// - Returns: An `AsyncSequence` of `BetterNotification` values.
|
||||
func notifications<BN: BetterNotification>(
|
||||
for betterType: BN.Type
|
||||
) -> any AsyncSequence<BN, Never> {
|
||||
notifications(named: BN.notificationName)
|
||||
.compactMap { $0.better() }
|
||||
}
|
||||
|
||||
/// Returns a Combine publisher that emits `BetterNotification` values of the given type.
|
||||
///
|
||||
/// - Parameter betterType: The `BetterNotification` type to observe.
|
||||
/// - Returns: A publisher emitting `BetterNotification` values.
|
||||
func publisher<BN: BetterNotification>(
|
||||
for betterType: BN.Type
|
||||
) -> AnyPublisher<BN, Never> {
|
||||
publisher(for: betterType.notificationName)
|
||||
.compactMap { $0.better() }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
27
Sources/NotificationSmuggling/Smuggled.swift
Normal file
27
Sources/NotificationSmuggling/Smuggled.swift
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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.
|
||||
///
|
||||
/// If you want to extract the contraband manually you can use the extension method ``Notification.smuggled()``.
|
||||
///
|
||||
/// When smuggling notifications you can use ``Notification.smuggle(:object:)`` to build up a notification with the correct
|
||||
/// user info automatically.
|
||||
public protocol Smuggled {}
|
||||
|
||||
public extension Smuggled {
|
||||
/// The notification name associated with the conforming type.
|
||||
///
|
||||
/// Uses the type's name to create a unique raw value in the format:
|
||||
/// `"NotificationSmuggler:{SelfType}"`.
|
||||
static var notificationName: Notification.Name {
|
||||
Notification.Name(rawValue: "NotificationSmuggler:\(Self.self)")
|
||||
}
|
||||
|
||||
/// The key used in the notification's `userInfo` dictionary to store the notification value.
|
||||
///
|
||||
/// Matches the raw value of `notificationName`.
|
||||
static var userInfoKey: String { notificationName.rawValue }
|
||||
}
|
||||
66
Sources/NotificationSmuggling/Smuggling.swift
Normal file
66
Sources/NotificationSmuggling/Smuggling.swift
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
|
||||
public extension Notification {
|
||||
/// Creates a `Notification` instance that smuggles the given `Smuggled` value.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - contraband: The `Smuggled` value to send.
|
||||
/// - object: An optional sender object.
|
||||
/// - Returns: A configured `Notification` with `name`, `object`, and `userInfo`.
|
||||
static func smuggle<Contraband: Smuggled>(
|
||||
_ contraband: Contraband,
|
||||
object: Any? = nil
|
||||
) -> Notification {
|
||||
Notification(
|
||||
name: Contraband.notificationName,
|
||||
object: object,
|
||||
userInfo: [Contraband.userInfoKey: contraband]
|
||||
)
|
||||
}
|
||||
|
||||
/// Extracts a `Smuggled` value (contraband) of the specified type from this `Notification`'s `userInfo`.
|
||||
///
|
||||
/// - Returns: The extracted `Smuggled` value when found, otherwise `nil`.
|
||||
func smuggled<Contraband: Smuggled>() -> Contraband? {
|
||||
guard let instance = userInfo?[Contraband.userInfoKey] else {
|
||||
NSLog("[\(Contraband.self)] Value not found in userInfo[\"\(Contraband.userInfoKey)\"]")
|
||||
return nil
|
||||
}
|
||||
guard let contraband = instance as? Contraband else {
|
||||
NSLog("[\(Contraband.self)] Failed to cast \(instance) as \(Contraband.self)")
|
||||
return nil
|
||||
}
|
||||
|
||||
return contraband
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public extension NotificationCenter {
|
||||
/// Returns an `AsyncSequence` of notifications of a specific `Smuggled` type.
|
||||
///
|
||||
/// Each element of the sequence is a `Smuggled` value.
|
||||
///
|
||||
/// - Parameter contrabandType: The `Smuggled` type to observe..
|
||||
/// - Returns: An `AsyncSequence` of `NotificationSmuggler` values.
|
||||
func notifications<Contraband: Smuggled>(
|
||||
for contrabandType: Contraband.Type
|
||||
) -> any AsyncSequence<Contraband, Never> {
|
||||
notifications(named: Contraband.notificationName)
|
||||
.compactMap { $0.smuggled() }
|
||||
}
|
||||
|
||||
/// Returns a Combine publisher that emits `Smuggled` values of the given type.
|
||||
///
|
||||
/// - Parameter contrabandType: The `Smuggled` type to observe.
|
||||
/// - Returns: A publisher emitting `Smuggled` values.
|
||||
func publisher<Contraband: Smuggled>(
|
||||
for contrabandType: Contraband.Type
|
||||
) -> AnyPublisher<Contraband, Never> {
|
||||
publisher(for: contrabandType.notificationName)
|
||||
.compactMap { $0.smuggled() }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import BetterNotification
|
||||
|
||||
struct HitchhikersNotification: BetterNotification, Equatable {
|
||||
let answer: Int
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import NotificationSmuggler
|
||||
|
||||
struct HitchhikersNotification: Smuggled, Equatable, Sendable {
|
||||
let answer: Int
|
||||
}
|
||||
13
Tests/NotificationSmugglingTests/SmuggledTests.swift
Normal file
13
Tests/NotificationSmugglingTests/SmuggledTests.swift
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
@testable import NotificationSmuggler
|
||||
import Testing
|
||||
|
||||
struct SmuggledTests {
|
||||
@Test func notificationName() {
|
||||
#expect(HitchhikersNotification.notificationName.rawValue == "NotificationSmuggler:HitchhikersNotification")
|
||||
}
|
||||
|
||||
@Test func userInfoKey() {
|
||||
#expect(HitchhikersNotification.userInfoKey == "NotificationSmuggler:HitchhikersNotification")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +1,42 @@
|
|||
@testable import BetterNotification
|
||||
import Combine
|
||||
import Foundation
|
||||
@testable import NotificationSmuggler
|
||||
import Testing
|
||||
|
||||
struct BetterNotificationTests {
|
||||
@Test func notificationName() {
|
||||
#expect(HitchhikersNotification.notificationName.rawValue == "BetterNotification:HitchhikersNotification")
|
||||
}
|
||||
struct SmugglingTests {
|
||||
|
||||
@Test func userInfoKey() {
|
||||
#expect(HitchhikersNotification.userInfoKey == "BetterNotification:HitchhikersNotification")
|
||||
}
|
||||
|
||||
// MARK: - Notification extensions
|
||||
// MARK: Notification extensions
|
||||
|
||||
@Test func buildNotification() {
|
||||
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||
let notification = Notification.better(hitchhikers)
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
let notification = Notification.smuggle(contraband)
|
||||
#expect(notification.name == HitchhikersNotification.notificationName)
|
||||
#expect(notification.userInfo?.count == 1)
|
||||
let key = HitchhikersNotification.userInfoKey
|
||||
let userInfoValue = notification.userInfo?[key] as? HitchhikersNotification
|
||||
#expect(userInfoValue == hitchhikers)
|
||||
#expect(userInfoValue == contraband)
|
||||
}
|
||||
|
||||
@Test func extractBetterPayload() {
|
||||
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||
let notification = Notification.better(hitchhikers)
|
||||
let extracted: HitchhikersNotification? = notification.better()
|
||||
#expect(extracted == hitchhikers)
|
||||
@Test func extractContraband() {
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
let notification = Notification.smuggle(contraband)
|
||||
let extracted: HitchhikersNotification? = notification.smuggled()
|
||||
#expect(extracted == contraband)
|
||||
}
|
||||
|
||||
@Test func extractBetterPayloadFailsOnWrongType() {
|
||||
@Test func extractContrabandFailsOnWrongType() {
|
||||
let imposter = Notification(
|
||||
name: HitchhikersNotification.notificationName,
|
||||
object: nil,
|
||||
userInfo: [HitchhikersNotification.userInfoKey: "imposter"]
|
||||
)
|
||||
let extracted: HitchhikersNotification? = imposter.better()
|
||||
let extracted: HitchhikersNotification? = imposter.smuggled()
|
||||
#expect(extracted == nil)
|
||||
}
|
||||
|
||||
@Test func extractBetterPayloadFailsOnMissingPayload() {
|
||||
let wrongNotification = Notification(name: HitchhikersNotification.notificationName)
|
||||
let extracted: HitchhikersNotification? = wrongNotification.better()
|
||||
@Test func extractContrabandFailsOnMissingPayload() {
|
||||
let incompleteNotification = Notification(name: HitchhikersNotification.notificationName)
|
||||
let extracted: HitchhikersNotification? = incompleteNotification.smuggled()
|
||||
#expect(extracted == nil)
|
||||
}
|
||||
|
||||
|
|
@ -58,16 +51,16 @@ struct BetterNotificationTests {
|
|||
let center = NotificationCenter()
|
||||
nonisolated(unsafe) var received = false
|
||||
Task {
|
||||
for await hitchhikers in center.notifications(for: HitchhikersNotification.self) {
|
||||
#expect(hitchhikers.answer == 42)
|
||||
for await contraband in center.notifications(for: HitchhikersNotification.self) {
|
||||
#expect(contraband.answer == 42)
|
||||
received = true
|
||||
break
|
||||
}
|
||||
}
|
||||
await Task.yield()
|
||||
|
||||
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||
center.post(.better(hitchhikers))
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
center.post(.smuggle(contraband))
|
||||
while !received { await Task.yield() }
|
||||
}
|
||||
|
||||
|
|
@ -107,8 +100,8 @@ struct BetterNotificationTests {
|
|||
}.store(in: &cancellables)
|
||||
await Task.yield()
|
||||
|
||||
let hitchhikers = HitchhikersNotification(answer: 42)
|
||||
center.post(.better(hitchhikers))
|
||||
let contraband = HitchhikersNotification(answer: 42)
|
||||
center.post(.smuggle(contraband))
|
||||
while !received { await Task.yield() }
|
||||
}
|
||||
|
||||
|
|
@ -118,8 +111,8 @@ struct BetterNotificationTests {
|
|||
let center = NotificationCenter()
|
||||
nonisolated(unsafe) var received = false
|
||||
center.publisher(for: HitchhikersNotification.self)
|
||||
.sink { hitchhikers in
|
||||
#expect(hitchhikers.answer == 42)
|
||||
.sink { contraband in
|
||||
#expect(contraband.answer == 42)
|
||||
received = true
|
||||
}.store(in: &cancellables)
|
||||
await Task.yield()
|
||||
Loading…
Reference in a new issue