A tiny, thread-safe publish/subscribe bus for Apple platforms — decouple components without wiring delegates everywhere.
Install · Quickstart · Highlights · API · Platforms
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.
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.
SwiftEventBus is distributed as a Swift package. That is the supported integration path for new apps and libraries.
- 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
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"),From an existing Swift package directory:
swift package resolve
swift build
swift test # when developing against a local checkout of this repo| 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 |
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.
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
}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.
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.
| 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).
| 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 |
SwiftEventBus.unregister(target) // all events for target
SwiftEventBus.unregister(target, name: "x") // one event name for targetSwiftEventBus 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.
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.
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
MIT © César Ferreira