Contents

antonsynd/swift-event-broadcasting

Event handling implementation for Swift

Features

  • "Set it and forget it" event subscription
  • Support for broadcasting multiple event types
  • Hassle-free unsubscribe mechanism for Hashable subscribers
  • Fully customizable event queueing and dispatching
  • Type-safe events with generic payload support
  • Async/await integration with AsyncStream
  • Combine support with Publisher APIs
  • Convenience methods: once, filter, map, and more
  • Debugging utilities: logging, introspection, and statistics
  • Subscriber counting and introspection utilities

Quick start

Create an event broadcaster

Extend EventBroadcaster or implement the EventBroadcasting protocol:

import Events

class GPSService: EventBroadcaster {
}

class GPSServiceAlternate: EventBroadcasting {
  private let broadcaster: EventBroadcaster = EventBroadcaster()

  func subscribe(...) { broadcaster.subscribe(...) }
  ...
  func broadcast(...) { broadcaster.broadcast(...) }
}

Subscribe to an event broadcaster

Subscribe with an event type, the event handler as a closure, and optionally an associated AnyHashable.

Without an AnyHashable, a SubscriberId will be returned. If you intend on unsubscribing (removing) the event handler, then you should store the subscriber id to call unsubscribe() later.

let gpsService = GPSService()

// Subscribe
let subscriberId = gpsService.subscribe(to: "locationUpdate") { event in
  print("location updated")
}

// Broadcast
gpsService.broadcast(Event(eventType: "locationUpdate"))
// prints "location updated"

// Unsubscribe
gpsService.unsubscribe(id: subscriberId, from: "locationUpdate")

With an AnyHashable, no subscriber id will be returned. To unsubscribe, pass the same AnyHashable.

let gpsService = GPSService()
let someHashable: AnyHashable = ...

// Subscribe
gpsService.subscribe(someHashable, to: "locationUpdate") { event in
  print("location updated")
}

// Broadcast
gpsService.broadcast(Event(eventType: "locationUpdate"))
// prints "location updated"

// Unsubscribe
gpsService.unsubscribe(subscriber: someHashable, from: "locationUpdate")

Advanced Features

Type-Safe Events

Use TypedEvent for compile-time type safety with event payloads:

// Define a custom event type
struct LocationData {
  let latitude: Double
  let longitude: Double
}

let eventType = "locationUpdate"

// Subscribe with type safety
gpsService.subscribe(to: eventType) { (event: TypedEvent<LocationData>) in
  print("Location: \(event.payload.latitude), \(event.payload.longitude)")
}

// Broadcast with typed data
let location = LocationData(latitude: 37.7749, longitude: -122.4194)
gpsService.broadcast(TypedEvent(eventType: eventType, payload: location))

Async/Await Support

Stream events using modern Swift concurrency:

// Stream all events of a type
Task {
  for await event in gpsService.events(for: "locationUpdate") {
    print("Received event: \(event)")
  }
}

// Wait for a single event
Task {
  let event = await gpsService.nextEvent(for: "locationUpdate")
  print("Got event: \(event)")
}

Combine Integration

Use Combine publishers for reactive programming:

import Combine

// Create a publisher for events
let cancellable = gpsService.publisher(for: "locationUpdate")
  .sink { event in
    print("Received: \(event)")
  }

// Type-safe publisher
let typedCancellable = gpsService.typedPublisher(for: "locationUpdate")
  .map { (event: TypedEvent<LocationData>) in event.payload }
  .sink { location in
    print("Location: \(location.latitude), \(location.longitude)")
  }

Convenience Methods

One-time subscription
// Subscribe to receive only the first event
gpsService.once(to: "locationUpdate") { event in
  print("First location update received")
}
Filtered subscription
// Only receive events that match a condition
gpsService.subscribe(
  to: "locationUpdate",
  filter: { event in
    guard let typed = event as? TypedEvent<LocationData> else { return false }
    return typed.payload.latitude > 0
  }
) { event in
  print("Northern hemisphere location: \(event)")
}
Mapped subscription
// Transform events before handling
gpsService.subscribe(
  to: "locationUpdate",
  map: { event -> String? in
    guard let typed = event as? TypedEvent<LocationData> else { return nil }
    return "\(typed.payload.latitude),\(typed.payload.longitude)"
  }
) { coordinates in
  print("Coordinates: \(coordinates)")
}

Introspection and Cleanup

// Check subscriber count
let count = gpsService.subscriberCount(for: "locationUpdate")
print("Subscribers: \(count)")

// Get all event types with subscribers
let types = gpsService.subscribedEventTypes()
print("Active event types: \(types)")

// Remove all subscribers for an event type
gpsService.unsubscribeAll(from: "locationUpdate")

// Remove all subscribers for all events
gpsService.unsubscribeAll()

Custom Event Dispatching

Customize how events are dispatched by providing your own EventDispatching implementation:

class CustomDispatcher: EventDispatching {
  func dispatch(_ event: Event, using eventHandler: @escaping EventHandler) {
    // Custom dispatching logic
    DispatchQueue.main.async {
      eventHandler(event)
    }
  }
}

let service = GPSService(eventDispatcher: CustomDispatcher())

Debugging and Introspection

Debug Logging

Enable debug logging to see what's happening with your events:

let service = GPSService()

// Enable debug logging with default options (logs everything)
service.enableDebugLogging()

// Or customize what gets logged
service.enableDebugLogging(options: [.logBroadcasts, .logSubscriptions])

// Custom logger
service.enableDebugLogging { message in
    os_log("%{public}@", log: .default, type: .debug, message)
}

Debug Information

Get detailed information about broadcaster state:

// Print debug info to console
service.printDebugInfo()

// Get debug description as string
let description = service.debugDescription()

// Get statistics dictionary
let stats = service.statistics()
print("Total subscribers: \(stats["totalSubscribers"]!)")
print("Event types: \(stats["eventTypes"]!)")

Logging Event Dispatcher

Wrap any dispatcher with logging:

let baseDispatcher = DispatchQueueEventDispatcher.EventDispatcher()
let loggingDispatcher = LoggingEventDispatcher(wrapping: baseDispatcher) { message in
    print("Event: \(message)")
}

let service = GPSService(eventDispatcher: loggingDispatcher)

Package Metadata

Repository: antonsynd/swift-event-broadcasting

Stars: 2

Forks: 0

Open issues: 0

Default branch: mainline

Primary language: swift

License: MIT

README: README.md