Skip to content

Instantly share code, notes, and snippets.

@glukianets
Last active October 6, 2025 11:03
Show Gist options
  • Save glukianets/74012f690a6621a43fb9496e27d2635e to your computer and use it in GitHub Desktop.
Save glukianets/74012f690a6621a43fb9496e27d2635e to your computer and use it in GitHub Desktop.
An idiomatic swift wrapper for Darwin's notification.h api
import notify
import Darwin
import Dispatch
import Synchronization
public actor DarwinNotifications<State> where State: RawRepresentable & Sendable, State.RawValue == UInt64 {
private let name: String
private var token: Token?
public nonisolated let unownedExecutor: UnownedSerialExecutor
private let queue: DispatchSerialQueue
private var continuations: [CheckedContinuation<Element?, Never>]
private let ordinal: Atomic<Int> = .init(-1)
public init(name: String) throws(Error) {
self.name = name
self.queue = .init(label: "Darwin Notification Queue for \(name)")
self.unownedExecutor = queue.asUnownedSerialExecutor()
self.continuations = []
do {
try self.queue.sync {
try self.assumeIsolated { (self: isolated DarwinNotifications) in
try self.resubscribe()
}
}
} catch let error as Error {
throw error
} catch {
fatalError("Unexpected error: \(error)")
}
}
isolated deinit {
try! self.unsubscribe() // best effort to handle errors
}
public static func post(_ name: String) throws where State == NoState {
try name.withCString { name in
let status = notify_post(name)
if let error = Error(rawValue: status) {
throw error
}
}
}
public func cancel() throws(Error) {
try self.unsubscribe()
}
private func receive(_ token: Int32) {
guard self.token != nil else { return }
assert(self.token?.rawValue == token)
let (_, newValue) = self.ordinal.add(1, ordering: .sequentiallyConsistent)
self.resumeAll(with: self.element(ordinal: newValue))
}
private func resumeAll(with element: Element?) {
var continuations: [CheckedContinuation<Element?, Never>] = []
swap(&continuations, &self.continuations)
for continuation in continuations {
continuation.resume(returning: element)
}
}
fileprivate func next(after: Int) async -> Element? {
let current = self.ordinal.load(ordering: .sequentiallyConsistent)
guard current <= after else { return self.element(ordinal: current) }
guard self.token != nil else { return nil }
return await withCheckedContinuation(isolation: self) { continuation in
self.continuations.append(continuation)
}
}
private func element(ordinal: Int) -> Element {
Element(
name: self.name,
ordinal: ordinal,
state: Result { () throws(Error) -> State? in try self.globalState }
)
}
private func unsubscribe() throws(Error) {
guard let token else { return }
defer { self.token = nil; self.resumeAll(with: nil) }
if let error = Error(rawValue: notify_cancel(token.rawValue)) {
throw error
}
}
private func resubscribe() throws(Error) {
do {
self.token = try name.withCString { name in
var rawToken: Int32 = NOTIFY_TOKEN_INVALID
let rawError = notify_register_dispatch(name, &rawToken, self.queue, { [unowned self] value in
self.receive(value)
})
if let error = Error(rawValue: rawError) { throw error }
guard let token = Token(rawValue: rawToken) else { throw Error.failed }
return token
}
} catch let error as Error {
throw error
} catch {
fatalError("Unexpected error: \(error)")
}
}
}
extension DarwinNotifications {
public var globalState: State? {
get throws(Error) {
guard let token else { throw Error.invalidToken }
var rawState: UInt64 = 0
let rawError = notify_get_state(token.rawValue, &rawState)
if let error = Error(rawValue: rawError) { throw error }
return .init(rawValue: rawState)
}
}
public func setGlobalState(_ newValue: State) throws(Error) {
guard let token else { throw Error.invalidToken }
let rawError = notify_set_state(token.rawValue, newValue.rawValue)
if let error = Error(rawValue: rawError) { throw error }
}
}
extension DarwinNotifications {
public struct Error: RawRepresentable & Sendable & Hashable & Swift.Error {
public static var invalidName: Self { Self(NOTIFY_STATUS_INVALID_NAME) }
public static var invalidToken: Self { Self(NOTIFY_STATUS_INVALID_TOKEN) }
public static var invalidPort: Self { Self(NOTIFY_STATUS_INVALID_PORT) }
public static var invalidFile: Self { Self(NOTIFY_STATUS_INVALID_FILE) }
public static var invalidSignal: Self { Self(NOTIFY_STATUS_INVALID_SIGNAL) }
public static var invalidRequest: Self { Self(NOTIFY_STATUS_INVALID_REQUEST) }
public static var notAuthorized: Self { Self(NOTIFY_STATUS_NOT_AUTHORIZED) }
public static var optDisable: Self { Self(NOTIFY_STATUS_OPT_DISABLE) }
public static var serverNotFound: Self { Self(NOTIFY_STATUS_SERVER_NOT_FOUND) }
public static var nullInput: Self { Self(NOTIFY_STATUS_NULL_INPUT) }
public static var failed: Self { Self(NOTIFY_STATUS_FAILED) }
public let rawValue: UInt32
public init?(rawValue: UInt32) {
guard rawValue != NOTIFY_STATUS_OK else { return nil }
self.rawValue = rawValue
}
private init(_ value: Int32) {
precondition(value >= 0)
self.init(rawValue: UInt32(value))!
}
}
fileprivate struct Token: Sendable {
public let rawValue: Int32
internal init?(rawValue: Int32) {
guard rawValue != NOTIFY_TOKEN_INVALID else { return nil }
self.rawValue = rawValue
}
}
}
extension DarwinNotifications: AsyncSequence {
public struct Element: Sendable {
public let name: String
public let ordinal: Int
public let state: Result<State?, Error>
fileprivate init(name: String, ordinal: Int, state: Result<State?, Error>) {
self.name = name
self.ordinal = ordinal
self.state = state
}
}
public struct AsyncIterator: AsyncIteratorProtocol {
public typealias Element = DarwinNotifications.Element
public typealias Failure = Never
private let subscription: DarwinNotifications
private var ordinal: Int
fileprivate init(subscription: DarwinNotifications, ordinal: Int) {
self.subscription = subscription
self.ordinal = ordinal
}
public mutating func next() async throws -> Self.Element? {
guard let result = await self.subscription.next(after: self.ordinal) else { return nil }
self.ordinal = result.ordinal
return result
}
}
nonisolated public func makeAsyncIterator() -> AsyncIterator {
.init(subscription: self, ordinal: self.ordinal.load(ordering: .sequentiallyConsistent))
}
}
public struct NoState: RawRepresentable & Sendable {
public typealias RawValue = UInt64
public var rawValue: UInt64 {
fatalError("unreachable")
}
public init?(rawValue: UInt64) {
nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment