Skip to content

Instantly share code, notes, and snippets.

@voxels
Created November 14, 2025 12:03
Show Gist options
  • Select an option

  • Save voxels/ad89211dde6ee129d7e891d17db51081 to your computer and use it in GitHub Desktop.

Select an option

Save voxels/ad89211dde6ee129d7e891d17db51081 to your computer and use it in GitHub Desktop.
import Foundation
import Combine
import SwiftUI
/// An `ObservableObject` ViewModel that subscribes to a `CDJ3000Manager`
/// and publishes its state for a SwiftUI View.
///
/// This class is marked with `@MainActor`, which ensures that all UI-related
/// state updates are automatically published on the main thread.
@MainActor
public final class DeckViewModel: ObservableObject {
/// The latest, published state of the CDJ. SwiftUI views can
/// bind to this property to get real-time updates.
@Published public private(set) var state: CDJ3000State
/// A reference to the underlying model (the hardware manager actor)
private let manager: CDJ3000Manager
/// The running task that subscribes to the manager's `stateStream`.
private var stateSubscriptionTask: Task<Void, Never>?
/// Initializes the ViewModel.
/// - Parameter manager: The `CDJ3000Manager` (actor) to observe.
public init(manager: CDJ3000Manager) {
self.manager = manager
// We must initialize `state` with a default value.
// The task below will immediately fetch and update it.
// We'll pass a temporary deckID; it will be corrected in a moment.
self.state = CDJ3000State(deckID: -1)
// Start a new Task to perform the asynchronous setup.
self.stateSubscriptionTask = Task {
// 1. Fetch the initial, up-to-date state from the actor.
let initialState = await manager.state
// 2. Set our @Published property.
// (We are on the MainActor, so this is UI-safe).
self.state = initialState
// 3. Start the long-running observation loop.
// This will continuously update our state as new
// messages come in from the message queue.
await observeManager()
}
}
/// A private function that creates the long-running observation loop
/// for the manager's "message queue" (its `stateStream`).
private func observeManager() async {
// Asynchronously loop over every new state published by the manager.
for await newState in manager.stateStream {
// Because this class is `@MainActor`, this assignment
// is guaranteed to be on the main thread, safely
// publishing the change to any observing SwiftUI View.
self.state = newState
}
}
/// Call this method to explicitly cancel the observation task.
/// (e.g., in a view's `.onDisappear` modifier).
public func cancelObservation() {
stateSubscriptionTask?.cancel()
stateSubscriptionTask = nil
}
deinit {
// Automatically cancel the task when the ViewModel is deallocated
// to prevent memory leaks.
cancelObservation()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment