Created
November 14, 2025 12:03
-
-
Save voxels/ad89211dde6ee129d7e891d17db51081 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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