# How AppEventStream Facilitates App-Level Events in BitChat

> Learn how AppEventStream in BitChat uses Swift actors and AsyncStream to create a type-safe, asynchronous broadcast for app-level events, decoupling layers.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-23

---

**AppEventStream is a Swift actor that provides a type-safe, asynchronous broadcast mechanism for app-wide events in BitChat, enabling loose coupling between UI, networking, and system integration layers through AsyncStream continuations.**

The `permissionlesstech/bitchat` repository implements a robust event-driven architecture to handle application-wide notifications ranging from deep link processing to TOR lifecycle changes. At the center of this system sits `AppEventStream`, which replaces traditional delegate patterns with structured concurrency primitives defined in [`bitchat/App/AppArchitecture.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/AppArchitecture.swift).

## Defining Typed Events with the AppEvent Enum

In [`/bitchat/App/AppArchitecture.swift`](https://github.com/permissionlesstech/bitchat/blob/main//bitchat/App/AppArchitecture.swift) (lines 20-31), the `AppEvent` enum enumerates every high-level occurrence the application tracks. This strongly-typed definition eliminates the fragility of string-based notification patterns by providing compile-time safety for event handling.

The enum includes cases such as:

- `.launched` – Application startup completion
- `.scenePhaseChanged` – SwiftUI scene phase transitions
- `.openedURL` – Deep link or universal URL handling
- `.torLifecycleChanged` – TOR network state updates
- `.terminationRequested` – Application shutdown signals

## The AppEventStream Actor Architecture

The `AppEventStream` actor, implemented in [`AppArchitecture.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppArchitecture.swift) (lines 33-40), owns a private dictionary of `AsyncStream<AppEvent>.Continuation` objects keyed by `UUID`. This actor-based design isolates mutable state from concurrent access, ensuring thread-safe subscription management across the application.

### Broadcasting Events via Continuations

When the `emit(_:)` method receives an `AppEvent`, it iterates over all stored continuations and calls `yield()` to dispatch the event asynchronously. This broadcast pattern decouples the emission source from consumers, allowing the TOR service, UI layer, and background workers to react independently.

```swift
// Conceptual implementation detail from AppArchitecture.swift
func emit(_ event: AppEvent) {
    for continuation in continuations.values {
        continuation.yield(event)
    }
}

```

### Managing Subscriber Lifecycles

The public `listen()` method creates an `AsyncStream` that registers its continuation in the actor's dictionary. Each continuation includes an `onTermination` handler that automatically removes the entry when a subscriber cancels, preventing memory leaks.

```swift
// From AppArchitecture.swift
func listen() -> AsyncStream<AppEvent> {
    AsyncStream { continuation in
        let id = UUID()
        continuations[id] = continuation
        continuation.onTermination = { _ in 
            self.continuations.removeValue(forKey: id) 
        }
    }
}

```

## Runtime Integration in AppRuntime

`AppRuntime` centralizes event stream ownership in [`/bitchat/App/AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main//bitchat/App/AppRuntime.swift) (lines 16-46). The singleton pattern exposes `let events = AppEventStream()` and provides the `record(_ event: AppEvent)` helper method, which serves as the primary interface for emitting events throughout the codebase.

Developers emit events by calling:

```swift
AppRuntime.shared.record(.openedURL("https://example.com"))

```

## Subscribing to Events in Practice

Components obtain an `AsyncStream<AppEvent>` by invoking `listen()` on the shared runtime instance. Subscribers typically process events within `Task` blocks using `for await` loops, enabling reactive patterns without direct object references.

```swift
// Subscribing in a view model or service
func startListening() {
    Task {
        for await event in AppRuntime.shared.events.listen() {
            switch event {
            case .openedURL(let url):
                handleDeepLink(url)
            case .torLifecycleChanged(let lifecycle):
                updateTorStatus(lifecycle)
            default:
                break
            }
        }
    }
}

```

## Architectural Benefits

- **Type Safety**: The `AppEvent` enum guarantees that subscribers handle only defined event types, catching errors at compile time rather than runtime.
- **Concurrency Isolation**: The actor model ensures safe access to the `continuations` dictionary across multiple concurrent subscribers.
- **Decoupling**: System integration code (e.g., TOR management) and UI components communicate through the stream rather than maintaining direct references or delegate chains.
- **Extensibility**: Adding new app-level events requires only extending the `AppEvent` enum and calling `record(_:)`, without modifying existing subscriber logic.

## Summary

- `AppEventStream` in [`/bitchat/App/AppArchitecture.swift`](https://github.com/permissionlesstech/bitchat/blob/main//bitchat/App/AppArchitecture.swift) (lines 33-40) is an actor that manages `AsyncStream` continuations for type-safe event broadcasting.
- The `AppEvent` enum defines strongly-typed cases including `.openedURL` and `.torLifecycleChanged` in [`AppArchitecture.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppArchitecture.swift) (lines 20-31).
- `AppRuntime` owns the singleton stream instance and exposes the `record(_:)` emission interface in [`/bitchat/App/AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main//bitchat/App/AppRuntime.swift) (lines 16-46).
- Subscribers use `for await` loops on streams obtained via `listen()`, enabling reactive patterns without delegate callbacks.
- Actor isolation guarantees thread-safe access to the internal continuations dictionary, while termination handlers prevent memory leaks.

## Frequently Asked Questions

### How do I emit an event using AppEventStream in BitChat?

Call `AppRuntime.shared.record(_:)` from any context, passing an `AppEvent` case such as `.openedURL("https://example.com")`. This method defined in [`AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppRuntime.swift) forwards the event to the singleton `AppEventStream` actor, which broadcasts it to all active subscribers by yielding to their continuations.

### What Swift concurrency features does AppEventStream utilize?

The implementation leverages Swift's `actor` model for isolation, `AsyncStream` for backpressure-aware event delivery, and `AsyncStream.Continuation` for bridging between callback-based systems and structured concurrency. These features are implemented in [`AppArchitecture.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppArchitecture.swift) (lines 33-40).

### How does the system prevent memory leaks when components unsubscribe?

The `listen()` method assigns a termination handler via `continuation.onTermination`, which removes the continuation from the actor's dictionary when the subscriber cancels or the stream deallocates. This automatic cleanup ensures the `continuations` dictionary does not grow unbounded.

### Where should I subscribe to AppEvents in the codebase?

View models and service layers should subscribe during initialization using `AppRuntime.shared.events.listen()` and process events in dedicated `Task` blocks. This pattern keeps UI components decoupled from the networking and system integration layers that emit events via `record(_:)`.