How Vorssaint Manages Feature State Using Combine
Vorssaint uses Combine's ObservableObject pattern with a singleton FeatureRuntime class that publishes state changes via @Published, enabling reactive synchronization across SwiftUI views and background services.
Vorssaint's feature-state architecture centers on reactive programming through Apple's Combine framework. The implementation in vorssaint/vorssaint-utils eliminates manual state polling by broadcasting changes through a centralized runtime, ensuring UI components and services respond instantly to configuration updates.
The Core Architecture: FeatureRuntime
The FeatureRuntime class in [Sources/Vorssaint/App/FeatureRuntime.swift](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/App/FeatureRuntime.swift) serves as the single source of truth for all feature availability.
Key implementation details:
- Singleton pattern: Accessed via
FeatureRuntime.shared ObservableObjectconformance: Required for Combine integration@Published private(set) var revision: The reactive trigger that increments on every state mutation
When any feature's availability changes, the finishAvailabilityChange() method increments revision. This single property drives the entire reactive pipeline.
// Conceptual runtime structure as implemented in FeatureRuntime.swift
final class FeatureRuntime: ObservableObject {
static let shared = FeatureRuntime()
@Published private(set) var revision: UInt = 0
private init() {}
func setAvailable(_ feature: AppFeature, _ available: Bool) {
// Update UserDefaults, execute binding closures, then:
finishAvailabilityChange()
}
private func finishAvailabilityChange() {
revision += 1
}
}
Observing State in SwiftUI Views
SwiftUI views consume the runtime through @ObservedObject, creating automatic subscriptions to revision changes.
The [Sources/Vorssaint/UI/Settings/MonitorSettings.swift](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/UI/Settings/MonitorSettings.swift) implementation demonstrates this pattern for the "Combine usage and temperature" toggle and similar controls:
struct FeatureHubView: View {
@ObservedObject private var runtime = FeatureRuntime.shared
var body: some View {
List {
ForEach(AppFeature.allCases) { feature in
Toggle(feature.title,
isOn: Binding(
get: { feature.isAvailable },
set: { runtime.setAvailable(feature, $0) }))
}
}
}
}
The @ObservedObject wrapper establishes a Combine subscriber. When revision increments, SwiftUI invalidates the view and triggers a rebuild—no explicit .onChange handler required for basic refresh behavior.
Binding Features to UserDefaults
Feature state persists through @AppStorage keys defined in [Sources/Vorssaint/Core/Defaults.swift](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/Core/Defaults.swift). The runtime maintains a static bindings dictionary mapping features to reactive closures.
The flow works as follows:
- User toggles a feature in UI
setAvailable()writes to the correspondingUserDefaultskey- The binding closure executes, calling
syncWithPreferences()on the relevant service finishAvailabilityChange()incrementsrevision- All subscribers receive the update
// Triggering a state change programmatically
FeatureRuntime.shared.setAvailable(.monitorCPU, true)
This single call cascades through: preference storage → service synchronization → UI refresh.
Service-Level Combine Integration
Background services in Vorssaint import Combine directly and subscribe to runtime changes. Services like WindowMaximizer and AutoQuitService implement syncWithPreferences() methods that react to the revision stream.
A typical service implementation:
final class WindowMaximizer {
static let shared = WindowMaximizer()
private var cancellable: AnyCancellable?
private init() {
cancellable = FeatureRuntime.shared.$revision
.sink { [weak self] _ in
self?.syncWithPreferences()
}
}
func syncWithPreferences() {
// Read current FeatureRuntime state and UserDefaults
// Start or stop maximizer logic based on availability
}
}
The $revision syntax accesses the Published wrapper's Publisher interface, creating a type-erased AnyCancellable subscription that survives for the service's lifetime.
Command-Bar Synchronization
The [Sources/Vorssaint/App/CommandBarService.swift](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/App/CommandBarService.swift) demonstrates cross-cutting reactive behavior. After each revision increment, CommandBarService.shared.noteHubChange() re-evaluates available feature rows using internal Combine publishers.
This ensures the command palette—often detached from the main view hierarchy—remains synchronized with ground-truth state without direct view-to-view communication.
Why Combine Over Alternatives
Vorssaint's Combine-based approach provides specific advantages for feature state:
| Aspect | Implementation |
|---|---|
| Propagation speed | Synchronous publisher delivery |
| Memory safety | Automatic cancellation via AnyCancellable |
| Threading | Receive on RunLoop.main for UI updates |
| Composability | Operators chain for complex derived state |
The @Published property wrapper abstracts away PassthroughSubject or CurrentValueSubject boilerplate while maintaining full Combine compatibility for services requiring manual subscription control.
Summary
FeatureRuntimeacts as the centralizedObservableObjectwith a@Published revisioncounter- SwiftUI views use
@ObservedObjectfor automatic refresh on state changes - Services subscribe directly to
$revisionviasinkto trigger background work - UserDefaults provides persistence, with reactive bindings triggering service updates
- Single revision increment propagates changes instantly across all system layers
Frequently Asked Questions
What triggers a UI refresh in Vorssaint's feature system?
The @Published var revision in FeatureRuntime triggers refreshes. Any SwiftUI view observing the runtime via @ObservedObject automatically rebuilds when revision increments, which occurs after every setAvailable() call through finishAvailabilityChange().
How do background services know when to start or stop?
Services subscribe to FeatureRuntime.shared.$revision using Combine's sink operator. When the revision changes, the subscription executes syncWithPreferences(), allowing the service to read current availability and adjust its operation accordingly.
Why use a revision counter instead of @Published properties for each feature?
A single revision counter reduces publisher overhead and simplifies subscription management. Services and views rarely need granular per-feature updates; they typically re-scan all relevant preferences when any feature changes. This trade-off minimizes Combine subscription complexity across the codebase.
Where is feature availability actually stored?
Availability persists in UserDefaults via @AppStorage keys defined in Sources/Vorssaint/Core/Defaults.swift. The runtime reads and writes these keys while maintaining the reactive ObservableObject layer for in-memory synchronization.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →