AsyncMonitor is a Swift library that provides a simple and easy-to-use way to manage Swift concurrency Task
s that observe async sequences. The AsyncMonitor
class allows you to create tasks that observe streams and call the given closure with each new value, and optionally also with a context parameter so you don't have to manage its lifetime.
It uses a Swift Task
to ensure that all resources are properly cleaned up when the AsyncMonitor
is cancelled or deallocated.
That's it. It's pretty trivial. I just got tired of writing it over and over, mainly for notifications. You still have to map your Notification
s to something sendable, which brings me to another point. This package pairs nicely with NotificationSmuggler for a complete notification handling system in the Swift 6 concurrency world.
The simplest example uses a closure that receives the notification. The closure is async so you can await in there if you need to.
import AsyncMonitor
class SimplestVersion {
let cancellable = NotificationCenter.default
.notifications(named: .NSCalendarDayChanged)
.map(\.name)
.monitor { _ in
print("The date is now \(Date.now)")
}
}
This example uses the context parameter to avoid reference cycles with self
.
class WithContext {
var cancellables = Set<AnyAsyncCancellable>()
init() {
NotificationCenter.default
.notifications(named: .NSCalendarDayChanged)
.map(\.name)
.monitor(context: self) { _self, _ in
_self.dayChanged()
}.store(in: &cancellables)
}
func dayChanged() {
print("The date is now \(Date.now)")
}
}
Working with Combine publishers is trivial thanks to AnyPublisher.values
.
import Combine
class CombineExample {
var cancellables = Set<AnyAsyncCancellable>()
init() {
Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.values
.monitor { date in
print("Timer fired at \(date)")
}
.store(in: &cancellables)
}
}
When you need to observe an object that uses KVO there's an extension method you can use to monitor it:
class KVOExample {
var cancellables = Set<AnyAsyncCancellable>()
init() {
let progress = Progress(totalUnitCount: 42)
progress.monitorValues(for: \.fractionCompleted) { fraction in
print("Progress is \(fraction.formatted(.percent))%")
}.store(in: &cancellables)
}
}
The only way to install this package is with Swift Package Manager (SPM). Please file a new issue or submit a pull-request if you want to use something else.
This package is supported on iOS 17.0+ and macOS 14.0+.
When you're integrating this into an app with Xcode then go to your project's Package Dependencies and enter the URL https://github.com/samsonjs/AsyncMonitor
and then go through the usual flow for adding packages.
When you're integrating this using SPM on its own then add this to the list of dependencies your Package.swift file:
.package(url: "https://github.com/samsonjs/AsyncMonitor.git", .upToNextMajor(from: "0.3.1"))
and then add "AsyncMonitor"
to the list of dependencies in your target as well.
Copyright © 2025 Sami Samhuri [email protected]. Released under the terms of the MIT License.