README.md
July 30, 2026 · View on GitHub
SwiftEventBus
A tiny, thread-safe publish/subscribe bus for Apple platforms — decouple components without wiring delegates everywhere.
Install · Quickstart · Highlights · API · Platforms
Why SwiftEventBus
NotificationCenter is built in, but stringly-typed observers, thread hops, and lifecycle cleanup get messy fast. SwiftEventBus wraps the same primitives with a small API: register on a target, post by name, unregister when the target goes away.
- Decouple senders and receivers. Components talk through event names, not direct references.
- Thread-aware delivery.
onMainThread,onBackgroundThread, or a customOperationQueue. - Main-thread posting.
postToMainThreadwhen work finishes off the UI queue. - Lifecycle-friendly. Track observers per target;
unregister(_:)tears them down in one call. - Foundation-only. No UIKit in the library — same API on iOS, macOS, tvOS, and watchOS via Swift Package Manager.
- Battle-tested. Built on
NotificationCenter; registration cache is guarded for concurrent use.
Quickstart
Add the package in Xcode (File → Add Package Dependencies…):
https://github.com/cesarferreira/SwiftEventBus.git
Then wire a subscriber and post an event:
import SwiftEventBus
final class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SwiftEventBus.onMainThread(self, name: "profileUpdated") { notification in
self.refreshUI(with: notification?.object)
}
}
func saveProfile() {
SwiftEventBus.post("profileUpdated", sender: currentUser)
}
deinit {
SwiftEventBus.unregister(self)
}
}
Rule of thumb: pass self (or another long-lived object) as the registration target and call unregister when that object is done — typically deinit or viewWillDisappear.
Install
SwiftEventBus is distributed as a Swift package. That is the supported integration path for new apps and libraries.
Xcode
- File → Add Package Dependencies…
- Enter
https://github.com/cesarferreira/SwiftEventBus.git - Choose Up to Next Major from
5.3.0(or a newer release tag) - Add the SwiftEventBus product to your app or framework target
Package.swift
Add the dependency to your package manifest:
dependencies: [
.package(url: "https://github.com/cesarferreira/SwiftEventBus.git", from: "5.3.0"),
],
targets: [
.target(
name: "MyApp",
dependencies: ["SwiftEventBus"]
),
]
Pin an exact version when you need reproducible builds:
.package(url: "https://github.com/cesarferreira/SwiftEventBus.git", exact: "5.3.0"),
Command line
From an existing Swift package directory:
swift package resolve
swift build
swift test # when developing against a local checkout of this repo
Platforms (SPM)
| Release | Swift | Minimum OS |
|---|---|---|
5.3.+ | 5 | iOS 12, macOS 10.15, tvOS 12, watchOS 6 |
5.2.+ | 5 | Same platform mins; prefer 5.3.+ for macOS Xcode framework fixes |
5.+ | 5 | See Package.swift platforms for the tag you pin |
| Older tags | 2–4 | Legacy; not recommended for new projects |
Legacy integrators
CocoaPods and Carthage are not actively maintained for this project anymore. The last published podspec (SwiftEventBus.podspec) and Xcode framework target remain in the repo for existing users, but new work should use SPM above.
Highlights
Payloads and filtering
Attach a sender object or userInfo like any notification:
SwiftEventBus.post("personFetched", sender: person)
SwiftEventBus.post("syncProgress", sender: nil, userInfo: ["percent": 42])
SwiftEventBus.onMainThread(self, name: "personFetched") { note in
let person = note?.object as! Person
print(person.name)
}
Register with a specific sender to only receive posts that use the same object:
SwiftEventBus.onBackgroundThread(self, name: "jobDone", sender: jobID) { _ in
// only fires when post(..., sender: jobID) matches
}
Background work → main UI
NotificationCenter delivers on the thread where you post. For UI updates, hop explicitly:
SwiftEventBus.onBackgroundThread(self, name: "fetchData") { _ in
let result = loadFromNetwork()
SwiftEventBus.postToMainThread("fetchDataDone", sender: result)
}
SwiftEventBus.onMainThread(self, name: "fetchDataDone") { note in
self.apply(result: note?.object)
}
Credit for the original pattern: @nunogoncalves.
macOS
Add the package in Xcode or your Package.swift (see Install). Example with AppKit:
import AppKit
import SwiftEventBus
final class MainViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
SwiftEventBus.onMainThread(self, name: "refresh") { _ in
self.view.needsLayout = true
}
}
deinit {
SwiftEventBus.unregister(self)
}
}
The Sample target in this repo is an iOS storyboard demo only; Mac apps should add the Swift package dependency.
API
Subscribe
| Method | Delivery |
|---|---|
onMainThread(_:name:sender:handler:) | OperationQueue.main |
onBackgroundThread(_:name:sender:handler:) | Background OperationQueue |
on(_:name:sender:queue:handler:) | Custom queue |
Handlers receive (Notification?) -> Void. Registration returns an observer token (usually you rely on unregister instead).
Post
| Method | Behavior |
|---|---|
post(_:sender:) | Post on the current thread |
post(_:sender:userInfo:) | Post with dictionary payload |
postToMainThread(_:sender:) | Async dispatch to main, then post |
postToMainThread(_:sender:userInfo:) | Same, with userInfo |
Unregister
SwiftEventBus.unregister(target) // all events for target
SwiftEventBus.unregister(target, name: "x") // one event name for target
Platforms
SwiftEventBus is tested on every push via GitHub Actions:
| Job | What it verifies |
|---|---|
| macOS (Swift Package Manager) | swift build / swift test on macOS (14 unit tests) |
| macOS (Xcode framework) | Framework build for generic/platform=macOS |
| iOS (Xcode framework) | Framework build for iOS |
| Sample iOS app | Sample app build, Simulator launch, smoke event path |
Requirements: Swift 5, Xcode 15+ recommended for local development.
Sample app
Open SwiftEventBus.xcodeproj, run the Sample scheme on an iOS Simulator. It exercises login flow events (loginCall → background work → login on the main thread). CI launches Sample with -SmokeTest to assert the bus end-to-end.
Contributing
Issues and PRs welcome. Please keep changes focused; CI must stay green.
- Fork and branch from
master - Run tests locally:
swift test(macOS or Linux Docker with Swift 5.10+) - Open a PR — the workflow runs SPM, iOS/macOS framework builds, and the Sample smoke test
License
MIT © César Ferreira