Any object or value can be sent as a notification. The recipient registers a handler
method for the type of object to receive.
An example:
struct ExampleNotification: Mail {
var info: Int
var other: Float
}
class ExampleRecipient {
init() {
PostOffice.default.register(self, ExampleRecipient.receive)
}
func receive(notification: ExampleNotification) {
// ... process the notification
}
}
// Send the notification ...
let recipient = ExampleRecipient()
PostOffice.default.post(ExampleNotification(info: 12, other: 15))
Posting notifications
Any object that implements the empty Mail protocol can be sent as a notification,
and only recipients registered for that notification type will receive it.
// Send a struct with the above example, or send an enum, or any other type.
enum ExampleEnum: Mail {
case fumble
case mumble(bumble: Int)
}
PostOffice.default.register { (mail: ExampleEnum) in
// ... process the notification
}
PostOffice.default.post(ExampleEnum.mumble(bumble: 12))
Observing notifications
There are multiple ways to receive notifications. All observers define the type of notification
that they want to receive, and only notifications matching those types will be received. If a
sender is specified, the type of that sender must also match.
Option 1: Register an object and method
Just as in NotificationCenter, the object is held weakly, and does not need to
be explicitly unregistered when the object deallocs.
class MyClass {
func init() {
PostOffice.default.register(self, MyClass.receive)
}
func receive(notification: ExampleNotification, sender: ExampleSender) {
// process the notification
}
}
Option 2: Register a block
A block or method can be passed into the PostOffice to observe notifications. Blocks
are held strongly inside the PostOffice, and must be unregistered explicitly.
class MyClass {
var token: RecipientId?
func init() {
PostOffice.default.register { [weak self] (notification: ExampleNotification) in
// process the notification
}
}
}
Unregistering
Every register() method will return a RecipientId, which can be used to unregister the
recipient.
let recipient = ExampleRecipient()
let id = PostOffice.default.register(recipient)
...
PostOffice.default.unregister(id)
Senders
Sending a notification can optionally include a sender as well. This is similar to NotificationCenter,
where recipients can optionally register for notifications sent only from a specific sender. In PonyExpress,
both the notification and sender are strongly typed.
Recipients can choose to include or exclude the sender parameter from the receiving block or method.
class ExampleRecipient {
init() {
PostOffice.default.register(self, ExampleRecipient.receiveWithOptionalSender)
PostOffice.default.register(self, ExampleRecipient.receiveWithSender)
PostOffice.default.register(self, ExampleRecipient.receiveWithoutSender)
}
// An optional sender will require that the sender of the notification either
// a) match the type of the `sender`, or b) be `nil`
func receiveWithOptionalSender(notification: ExampleNotification, sender: ExampleSender?) {
// ... process the notification
}
// An non-optional sender will require that the sender of the notification either match
// the `sender` type
func receiveWithSender(notification: ExampleNotification, sender: ExampleSender) {
// ... process the notification
}
// Omitting a `sender` parameter will receive notifications for senders of any type, even nil senders
func receiveWithoutSender(notification: ExampleNotification) {
// ... process the notification
}
}
// recipients can also register to receive notifications from a singular exact-match sender
let sender = ExampleSender()
let recipient = ExampleRecipient()
PostOffice.default.register(sender: sender, recipient, ExampleRecipient.receiveWithSender)
PostOffice.default.register(sender: sender, recipient, ExampleRecipient.receiveWithoutSender)
When posting a notification, a sender can optionally be provided.
When registering with a PostOffice, the recipient can choose which DispatchQueue to be notified on.
If no queue is specified, the notificaiton is sent synchronously on the queue that posts the notification. If
a queue is specified, the notification is sent asynchronously on that queue.
Notifications using NotificationCenter are sent through a [String: Any]userInfo property of the
notification. This means that any observesr for that notification need to decode the userInfo using
something like guard let myStuff = notification.userInfo["someProperty"] as? MyStuff.
This provides a number of problems:
"someProperty" could contain a typo. Using a constant is susceptible to copy/paste errors.
Notifications are verbose - they require a notification name, the userInfo keys, and the actual values
Values are not typesafe. Sending a Float and attempting to decoding a CGFloat will silently fail (or runtime error).
When recieving unexpected data, observers either siliently fail or crash at runtime.
In PonyExpress, the goal is to reduce verbosity and move errors from runtime to compile time.
Observers always receive the exact types they expect
Any errors are provided at compile time, guaranteeing runtime type safety
No extra String names or keys - only the actual data is sent without any extra boiler plate
Thanks! ❤️
Enjoying PonyExpress? Say thanks and buy me a coffee ☕️!
PonyExpress
PonyExpress
provides a type-safe alternative toNotificationCenter
.Documentation
View the documentation for
PonyExpress
. Documentation for Xcode can be built with the includedbuilddocs.sh
script.The
jq
tool is also needed to format the docc json files that are generated.Installation
PonyExpress
is available as a Swift package.https://github.com/adamwulf/PonyExpress.git
Quick Start
Any object or value can be sent as a notification. The recipient registers a handler method for the type of object to receive.
An example:
Posting notifications
Any object that implements the empty
Mail
protocol can be sent as a notification, and only recipients registered for that notification type will receive it.Observing notifications
There are multiple ways to receive notifications. All observers define the type of notification that they want to receive, and only notifications matching those types will be received. If a sender is specified, the type of that sender must also match.
Option 1: Register an object and method
Just as in
NotificationCenter
, the object is held weakly, and does not need to be explicitly unregistered when the object deallocs.Option 2: Register a block
A block or method can be passed into the
PostOffice
to observe notifications. Blocks are held strongly inside thePostOffice
, and must be unregistered explicitly.Unregistering
Every
register()
method will return aRecipientId
, which can be used to unregister the recipient.Senders
Sending a notification can optionally include a
sender
as well. This is similar toNotificationCenter
, where recipients can optionally register for notifications sent only from a specific sender. In PonyExpress, both the notification and sender are strongly typed.Recipients can choose to include or exclude the sender parameter from the receiving block or method.
When posting a notification, a sender can optionally be provided.
DispatchQueues
When registering with a
PostOffice
, the recipient can choose whichDispatchQueue
to be notified on. If no queue is specified, the notificaiton is sent synchronously on the queue that posts the notification. If a queue is specified, the notification is sent asynchronously on that queue.Motivation
Notifications using
NotificationCenter
are sent through a[String: Any]
userInfo
property of the notification. This means that any observesr for that notification need to decode the userInfo using something likeguard let myStuff = notification.userInfo["someProperty"] as? MyStuff
.This provides a number of problems:
"someProperty"
could contain a typo. Using a constant is susceptible to copy/paste errors.userInfo
keys, and the actual valuesFloat
and attempting to decoding aCGFloat
will silently fail (or runtime error).In
PonyExpress
, the goal is to reduce verbosity and move errors from runtime to compile time.String
names or keys - only the actual data is sent without any extra boiler plateThanks! ❤️
Enjoying
PonyExpress
? Say thanks and buy me a coffee ☕️!