Library / SDK
cesarferreira/SwiftEventBus avatar
cesarferreira/SwiftEventBus

SwiftEventBus: A Thread Aware Event Bus Wrapping NotificationCenter

A publish/subscribe EventBus optimized for iOS

1,121 stars109 forksSwiftMIT

At a glance

What is it?
SwiftEventBus is a small MIT licensed Swift package that wraps NotificationCenter with a typed API for registering on a target object, posting by name with an optional sender filter, choosing the delivery queue, and unregistering everything for a target in one call. It targets iOS, macOS, tvOS and watchOS through Swift Package Manager.
Who is it for?
SwiftEventBus fits a Swift codebase that has scattered one-off NotificationCenter observers and wants one place to register, filter by sender, control the delivery queue and tear observers down with the owning object, and it is a poor fit for a project that wants type safe events, since event names remain strings.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 85 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the Wrapper Adds Over NotificationCenter

SwiftEventBus is not a new event system. It is a thin layer over the Foundation NotificationCenter, and the README says so directly, describing it as wrapping the same primitives with a small API for registering on a target, posting by name and unregistering when the target goes away.

The problem it addresses is stated in the same breath: stringly-typed observers, thread hops and lifecycle cleanup get messy fast. NotificationCenter gives you an observer object, a name and a closure, and the friction comes from managing that observer by hand. Every subscription is a token you must keep, and every view controller that subscribes has to remember to remove itself.

The wrapper changes the ownership model. You register against a target object, and when that object is deallocated you call unregister with it, and everything registered for that object goes away in one call. The library is Foundation-only, with no UIKit in it, so the same API works on iOS, macOS, tvOS and watchOS. The README also notes that the registration cache is guarded for concurrent use, and the pattern it builds on is credited to @nunogoncalves.

Posting by Name With an Optional Sender Filter

Posting takes an event name and an optional sender object, and the payload travels the way it does in NotificationCenter. A sender can be any object:

swift
SwiftEventBus.post("personFetched", sender: person)
SwiftEventBus.post("syncProgress", sender: nil, userInfo: ["percent": 42])

The sender is not only a payload, it is also a filter. Registering with a specific sender means the handler only fires for posts that use the same object, which is what keeps two unrelated parts of an app from reacting to the same event name:

swift
SwiftEventBus.onBackgroundThread(self, name: "jobDone", sender: jobID) { _ in
    // only fires when post(..., sender: jobID) matches
}

That is a genuine improvement over raw NotificationCenter for a common pattern, where a generic name such as jobDone would otherwise wake up every listener in the process. The tradeoff is that the filter is identity based rather than type based, so a post from a different instance of the same class does not match, which is usually the behaviour you want and occasionally not.

Delivery Queues: Main, Background or One You Supply

Threading is the other half of the API, and it exists because NotificationCenter delivers on the thread where you post. Three registration methods cover the cases:

| Method | Delivery | | ------ | -------- | | `onMainThread(_:name:sender:handler:)` | `OperationQueue.main` | | `onBackgroundThread(_:name:sender:handler:)` | Background `OperationQueue` | | `on(_:name:sender:queue:handler:)` | Custom queue |

The background to main pattern is the common one in an app, where a network call finishes off the UI queue and the result has to come back before the view updates. The README shows the two-step version, with the background handler posting to the main thread and a main thread handler consuming it:

swift
SwiftEventBus.onBackgroundThread(self, name: "fetchData") { _ in
    let result = loadFromNetwork()
    SwiftEventBus.postToMainThread("fetchDataDone", sender: result)
}

postToMainThread is described as an asynchronous dispatch to the main queue followed by the post, and there are two variants of it, one with a sender and one with a userInfo dictionary. Handlers always receive an optional Notification, and registration returns an observer token, although the README notes that most people rely on unregister instead of holding onto it.

Unregistering by Target Object Rather Than by Token

The unregister API is the reason the library exists. Two forms are documented:

swift
SwiftEventBus.unregister(target)              // all events for target
SwiftEventBus.unregister(target, name: "x")   // one event name for target

The first form removes every registration made against that target, and the second narrows it to a single event name. The rule of thumb in the README is to pass self, or another long lived object, as the registration target, and to call unregister when that object is done, typically in deinit or viewWillDisappear.

The quickstart example puts that in a view controller, with the registration in viewDidLoad and the cleanup in deinit:

swift
deinit {
    SwiftEventBus.unregister(self)
}

Registering against a temporary object is the failure mode to avoid, because an observer registered against an object that never gets unregistered keeps the closure alive for the lifetime of the notification center. The library does not use weak references to make that safe, so the responsibility stays with the caller, and the README says so with the deinit example rather than burying it.

Installing Through Swift Package Manager

Swift Package Manager is the supported integration path for new apps and libraries. In Xcode the steps are File, Add Package Dependencies, then the repository URL, then the version rule and the product to add to the target:

text
https://github.com/cesarferreira/SwiftEventBus.git

In a manifest the dependency is declared like this:

swift
dependencies: [
    .package(url: "https://github.com/cesarferreira/SwiftEventBus.git", from: "5.3.0"),
],

When reproducible builds matter, the README shows the exact variant, using the exact: parameter with a fixed version:

swift
.package(url: "https://github.com/cesarferreira/SwiftEventBus.git", exact: "5.3.0"),

From the command line, the three commands are the standard Swift package ones, and the README notes that swift test is for when you are developing against a local checkout of this repository:

bash
swift package resolve
swift build
swift test   # when developing against a local checkout of this repo

The repository carries Package.swift, the SwiftEventBus/ source directory, a Tests/ directory, and the Sample/ app with LoginService.swift, Person.swift and ViewController.swift.

CocoaPods and Carthage Are Frozen for Existing Users

The install section carries a section titled Legacy integrators, and it is worth reading before you plan an integration. CocoaPods and Carthage are not actively maintained for this project anymore. The last published podspec and the Xcode framework target remain in the repository for existing users, and new work should use Swift Package Manager.

That is a specific kind of upgrade risk. A team that is still on a podspec can keep building, but is building against a path that no longer receives fixes, so a security or correctness change in the library will not reach them. Converting to Swift Package Manager is the documented path forward, and for a project that already uses SPM for other dependencies it is mostly a matter of moving the dependency declaration into Package.swift.

The macOS story has a similar caveat. The Sample target in the repository is an iOS storyboard demo only, so a Mac application has to add the Swift package dependency itself. The README shows the AppKit version of the same view controller pattern, registering on the main thread in viewDidLoad and unregistering in deinit.

Platform Minimums and the Five Year Gap in the Tag History

The platform table is precise about what each release line supports. The 5.3.+ line requires Swift 5 and a minimum of iOS 12, macOS 10.15, tvOS 12 and watchOS 6. The 5.2.+ line has the same platform minimums, and the README says to prefer 5.3.+ for macOS Xcode framework fixes, which is a hint that the framework target had a problem that the newer line resolves. The 5.+ line also requires Swift 5 and points you at the platforms declaration in Package.swift for the tag you pin. Older tags are on Swift 2 to 4 and are described as legacy and not recommended for new projects.

The tag history is unusual. Versions 5.2.0 and 5.3.0 were both published on 2026-07-30, minutes apart, and the release before them, 5.1.0, dates from 2021-03-17. So the library sat at 5.1 for roughly five years and then took two minor releases in the same afternoon.

The repository is not archived and the last push was on 2026-07-30, so the code is current. What the gap does mean is that anyone who pinned 5.1.0 for years is now choosing between a five year old baseline and a line that changed substantially in a single day, so reading the diff between 5.1.0 and 5.3.0 is the sensible first step for an existing user.

Editorial conclusion

SwiftEventBus fits a Swift codebase that has scattered one-off NotificationCenter observers and wants one place to register, filter by sender, control the delivery queue and tear observers down with the owning object, and it is a poor fit for a project that wants type safe events, since event names remain strings. Before adopting it, note that the library is Foundation-only with no UIKit dependency, that the Sample target is an iOS storyboard demo and macOS apps must add the package themselves, and that CocoaPods and Carthage are no longer maintained, so an existing podspec based integration needs converting to Swift Package Manager. The package is MIT licensed, the newest release is 5.3.0 from 2026-07-30, and the last push was on 2026-07-30.

Frequently asked questions

How do I register and unregister a SwiftEventBus observer?

Register on a target object with a method such as SwiftEventBus.onMainThread(self, name: "profileUpdated"), and call SwiftEventBus.unregister(self) when that object is done, typically in deinit. The README recommends passing self or another long lived object as the registration target.

How do I deliver an event on the main thread from background work?

Post with SwiftEventBus.postToMainThread("fetchDataDone", sender: result), which dispatches asynchronously to the main queue and then posts, and register the receiving side with SwiftEventBus.onMainThread. There is also a postToMainThread variant that carries a userInfo dictionary.

Can SwiftEventBus filter events by sender?

Yes. Registering with a specific sender only fires the handler for posts that use the same object, so SwiftEventBus.onBackgroundThread(self, name: "jobDone", sender: jobID) responds only to post calls that pass the same jobID.

Is CocoaPods still supported for SwiftEventBus?

Not actively. The README states that CocoaPods and Carthage are not maintained for this project anymore, that the last published podspec and the Xcode framework target remain for existing users, and that new work should use Swift Package Manager.

What are the minimum platform versions for SwiftEventBus 5.3?

The 5.3.+ line requires Swift 5 with iOS 12, macOS 10.15, tvOS 12 and watchOS 6 as minimums. The 5.2.+ line has the same platform minimums, and the README advises preferring 5.3.+ for macOS Xcode framework fixes.

Official sources

  1. cesarferreira/SwiftEventBus on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cesarferreira-swifteventbus.svg)](https://hysenlabs.com/projects/cesarferreira-swifteventbus)

Was this useful?