{"id":19932380,"url":"https://github.com/adamwulf/ponyexpress","last_synced_at":"2025-05-03T11:32:17.816Z","repository":{"id":61956818,"uuid":"529704097","full_name":"adamwulf/PonyExpress","owner":"adamwulf","description":"Type-safe NotificationCenter alternative for Swift","archived":false,"fork":false,"pushed_at":"2023-04-20T16:56:27.000Z","size":703,"stargazers_count":10,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-05-01T12:30:03.277Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adamwulf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"adamwulf","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2022-08-27T21:49:10.000Z","updated_at":"2024-08-06T00:34:26.083Z","dependencies_parsed_at":"2024-08-06T00:34:24.114Z","dependency_job_id":"a539aa0f-301b-4754-81d4-4a27a64e6753","html_url":"https://github.com/adamwulf/PonyExpress","commit_stats":{"total_commits":209,"total_committers":1,"mean_commits":209.0,"dds":0.0,"last_synced_commit":"3ae00d32de37993239ba4aaf95c78be6f70c7f63"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamwulf%2FPonyExpress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamwulf%2FPonyExpress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamwulf%2FPonyExpress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamwulf%2FPonyExpress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adamwulf","download_url":"https://codeload.github.com/adamwulf/PonyExpress/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224360230,"owners_count":17298319,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T23:09:58.971Z","updated_at":"2024-11-12T23:09:59.043Z","avatar_url":"https://github.com/adamwulf.png","language":"Swift","funding_links":["https://github.com/sponsors/adamwulf"],"categories":[],"sub_categories":[],"readme":"# PonyExpress\n\n`PonyExpress` provides a type-safe alternative to `NotificationCenter`.\n\n[![CI](https://github.com/adamwulf/PonyExpress/actions/workflows/swift.yml/badge.svg)](https://github.com/adamwulf/PonyExpress/actions/workflows/swift.yml)\n\n## Documentation\n\n[View the documentation](https://adamwulf.github.io/PonyExpress/documentation/ponyexpress/) for `PonyExpress`. Documentation for Xcode\ncan be built with the included `builddocs.sh` script.\n\n```bash\n$ ./builddocs.sh\n```\n\nThe `jq` tool is also needed to format the docc json files that are generated.\n\n```\n$ brew install jq\n```\n\n## Installation\n\n`PonyExpress` is available as a Swift package.\n\n```\n.package(url: \"https://github.com/adamwulf/PonyExpress.git\", .branch(\"main\"))\n```\n\nhttps://github.com/adamwulf/PonyExpress.git\n\n## Quick Start\n\nAny object or value can be sent as a notification. The recipient registers a handler\nmethod for the type of object to receive.\n\nAn example:\n\n```swift\nstruct ExampleNotification: Mail {\n    var info: Int\n    var other: Float\n}\n\nclass ExampleRecipient {\n    init() {\n        PostOffice.default.register(self, ExampleRecipient.receive)\n    }\n\n    func receive(notification: ExampleNotification) {\n        // ... process the notification\n    }\n}\n\n// Send the notification ...\nlet recipient = ExampleRecipient()\nPostOffice.default.post(ExampleNotification(info: 12, other: 15))\n```\n\n## Posting notifications\n\nAny object that implements the empty ``Mail`` protocol can be sent as a notification, \nand only recipients registered for that notification type will receive it.\n\n```swift\n// Send a struct with the above example, or send an enum, or any other type.\nenum ExampleEnum: Mail {\n    case fumble\n    case mumble(bumble: Int)\n}\n\nPostOffice.default.register { (mail: ExampleEnum) in\n    // ... process the notification\n}\n\nPostOffice.default.post(ExampleEnum.mumble(bumble: 12))\n```\n\n## Observing notifications\n\nThere are multiple ways to receive notifications. All observers define the type of notification\nthat they want to receive, and only notifications matching those types will be received. If a\nsender is specified, the type of that sender must also match.\n\n### Option 1: Register an object and method\n\nJust as in `NotificationCenter`, the object is held weakly, and does not need to\nbe explicitly unregistered when the object deallocs. \n\n```swift\nclass MyClass {\n    func init() {\n        PostOffice.default.register(self, MyClass.receive) \n    }\n    \n    func receive(notification: ExampleNotification, sender: ExampleSender) {\n        // process the notification\n    }\n}\n```\n\n### Option 2: Register a block\n\nA block or method can be passed into the ``PostOffice`` to observe notifications. Blocks\nare held strongly inside the ``PostOffice``, and must be unregistered explicitly.\n\n```swift\nclass MyClass {\n    var token: RecipientId? \n    \n    func init() {\n        PostOffice.default.register { [weak self] (notification: ExampleNotification) in\n            // process the notification\n        }\n    }\n}\n```\n\n## Unregistering\n\nEvery `register()` method will return a `RecipientId`, which can be used to unregister the\nrecipient.\n\n\n```swift\nlet recipient = ExampleRecipient()\nlet id = PostOffice.default.register(recipient)\n...\nPostOffice.default.unregister(id)\n```\n\n## Senders\n\nSending a notification can optionally include a `sender` as well. This is similar to `NotificationCenter`,\nwhere recipients can optionally register for notifications sent only from a specific sender. In PonyExpress,\nboth the notification and sender are strongly typed.\n\nRecipients can choose to include or exclude the sender parameter from the receiving block or method.\n\n```swift\nclass ExampleRecipient {\n    init() {\n        PostOffice.default.register(self, ExampleRecipient.receiveWithOptionalSender)\n        PostOffice.default.register(self, ExampleRecipient.receiveWithSender)\n        PostOffice.default.register(self, ExampleRecipient.receiveWithoutSender)\n    }\n\n    // An optional sender will require that the sender of the notification either\n    // a) match the type of the `sender`, or b) be `nil`\n    func receiveWithOptionalSender(notification: ExampleNotification, sender: ExampleSender?) {\n        // ... process the notification\n    }\n\n    // An non-optional sender will require that the sender of the notification either match\n    // the `sender` type\n    func receiveWithSender(notification: ExampleNotification, sender: ExampleSender) {\n        // ... process the notification\n    }\n\n    // Omitting a `sender` parameter will receive notifications for senders of any type, even nil senders\n    func receiveWithoutSender(notification: ExampleNotification) {\n        // ... process the notification\n    }\n}\n\n// recipients can also register to receive notifications from a singular exact-match sender\nlet sender = ExampleSender()\nlet recipient = ExampleRecipient()\nPostOffice.default.register(sender: sender, recipient, ExampleRecipient.receiveWithSender) \nPostOffice.default.register(sender: sender, recipient, ExampleRecipient.receiveWithoutSender) \n```\n\nWhen posting a notification, a sender can optionally be provided.\n\n```swift\nPostOffice.default.post(ExampleNotification(info: 12, other: 15), sender: sender)\n```\n\n## DispatchQueues\n\nWhen registering with a ``PostOffice``, the recipient can choose which `DispatchQueue` to be notified on.\nIf no queue is specified, the notificaiton is sent synchronously on the queue that posts the notification. If\na queue is specified, the notification is sent asynchronously on that queue.\n\n```swift\nPostOffice.default.register(queue: myDispatchQueue, recipient, MyClass.receive) \n```\n\n## Motivation\n\nNotifications using `NotificationCenter` are sent through a `[String: Any]` `userInfo` property of the \nnotification. This means that any observesr for that notification need to decode the userInfo using\nsomething like `guard let myStuff = notification.userInfo[\"someProperty\"] as? MyStuff`.\n\nThis provides a number of problems:\n\n- `\"someProperty\"` could contain a typo. Using a constant is susceptible to copy/paste errors.\n- Notifications are verbose - they require a notification name, the `userInfo` keys, and the actual values\n- Values are not typesafe. Sending a `Float` and attempting to decoding a `CGFloat` will silently fail (or runtime error).\n- When recieving unexpected data, observers either siliently fail or crash at runtime.\n\nIn `PonyExpress`, the goal is to reduce verbosity and move errors from runtime to compile time.\n\n- Observers always receive the exact types they expect\n- Any errors are provided at compile time, guaranteeing runtime type safety\n- No extra `String` names or keys - only the actual data is sent without any extra boiler plate\n\n## Thanks! ❤️\n\nEnjoying `PonyExpress`? [Say thanks](https://github.com/sponsors/adamwulf) and buy me a coffee ☕️!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamwulf%2Fponyexpress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadamwulf%2Fponyexpress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamwulf%2Fponyexpress/lists"}