Skip to content

Conversation

@bdunay3
Copy link
Contributor

@bdunay3 bdunay3 commented Feb 6, 2026

Description

This PR introduces AsyncVSM, a Swift Concurrency-based implementation of VSM, while preserving Legacy VSM for backward compatibility. AsyncVSM uses Swift 6 concurrency (async/await, @MainActor, @Observable) instead of Combine.

Key Changes

Architecture

  • New AsyncStateContainer using Swift Concurrency
  • Legacy VSM moved to Sources/LegacyVSM/ (Swift 5)
  • AsyncVSM in Sources/VSM/ (Swift 6)
  • Both coexist for migration

Core Implementation

  1. AsyncStateContainer (Sources/VSM/AsyncStateContainer.swift)

    • @Observable (replaces ObservableObject)
    • @MainActor for thread safety
    • Supports State, StateSequence, AsyncStream, AsyncSequence, and Combine Publisher
    • Debounced observation variants
    • Signposting for Instruments
    • Never-throwing design (Failure == Never)
  2. ViewState (Sources/VSM/ViewState.swift)

    • SwiftUI property wrapper using @Observable
    • "Init once" semantics via @State
    • Works with SwiftUI's observation system
  3. RenderedViewState (Sources/VSM/RenderedViewState.swift)

    • UIKit support for iOS 17–18
    • Deprecated in favor of ViewState for iOS 26+
    • Uses Observation framework for automatic rendering
  4. StateSequence (Sources/VSM/StateSequence.swift)

    • New type for multi-state sequences
    • Convenient API for common patterns

Thread Safety

  • @MainActor isolation ensures state changes on the main thread
  • Actor isolation provides thread safety without manual synchronization
  • All properties are atomic via actor isolation
  • @Sendable requirements for thread-safe closures

Migration Support

  • Legacy observeAsync method preserved (deprecated, redirects to observe)
  • Combine Publisher support for gradual migration
  • Legacy VSM remains available and tested

Developer Experience

  • Signposting for Instruments
  • Documentation updates
  • Demo app refactored to Structured Concurrency
  • Binding support for SwiftUI

Differences: Legacy VSM vs AsyncVSM

Aspect Legacy VSM AsyncVSM
Concurrency Model Combine (ObservableObject, @Published) Swift Concurrency (@Observable, @MainActor)
State Container StateContainer<State> AsyncStateContainer<State>
Observation API observe(_:), observeAsync(_:) Unified observe(_:) with multiple overloads
Thread Safety Manual setStateOnMainThread checks @MainActor isolation
State Sequences Combine Publisher only StateSequence, AsyncStream, AsyncSequence, Publisher
Debouncing Custom Combine-based implementation AsyncAlgorithms.debounce
Error Handling Can throw errors Never-throwing (Failure == Never)
SwiftUI Integration ObservableObject protocol @Observable macro
Performance Tracking Debug logging OSLog signposting for Instruments
Swift Version Swift 5 Swift 6
Minimum iOS iOS 13+ iOS 17+ (Observation framework)

Benefits

  1. Modern concurrency: Uses Swift Concurrency instead of Combine
  2. Better thread safety: @MainActor isolation
  3. More flexible: Supports multiple async sequence types
  4. Better performance: Signposting for profiling
  5. Future-proof: Aligned with Swift 6 and modern SwiftUI patterns
  6. Backward compatible: Legacy VSM remains available

Testing

  • All Legacy VSM tests preserved and passing
  • New tests for AsyncStateContainer
  • Demo app updated and tested
  • Thread safety verified

Documentation

  • Updated documentation with examples
  • Added LLM-friendly context for code generation
  • Migration guides and best practices

Breaking Changes

None. Legacy VSM remains fully functional. AsyncVSM is additive.

Next Steps

  • Migrate existing codebases to AsyncVSM
  • Legacy VSM will be maintained for compatibility
  • Consider deprecating Legacy VSM in a future major version

This PR represents a modernization of the VSM library, bringing it in line with Swift 6 and Swift Concurrency while maintaining backward compatibility.

Type of Change

  • Bug Fix
  • New Feature
  • Breaking Change
  • Refactor
  • Documentation
  • Other (please describe)

Checklist

  • I have read the contributing guidelines
  • Existing issues have been referenced (where applicable)
  • I have verified this change is not present in other open pull requests
  • Functionality is documented
  • All code style checks pass
  • New code contribution is covered by automated tests
  • All new and existing tests pass

Bill Dunay and others added 30 commits October 14, 2025 09:16
* Supports Swift 6+ compiler tools and language mode
* Will add in original VSM version once first draft has been vetted
…tainer is thread-safe and its proeprties atomic
…hing works will with Swift 6 and enforces thread safety
…restrictive and doesn’t by us anything.

UIViewController and UIView, which will use RenderedViewState is already Sendable and restricted to MainActor so making the closure that calls the “render” function Sendable doesn’t by us anything and just causes warnings.
…ode. Also removed the refresh method from RenderedViewState because it just doesn’t make sense in UIKit world.
Updated the stateChangeStream function to timeout after a set amount of time. Also made sure that when AsyncStateContainer goes out of scope the active stateChangeStream finishes so it doesn’t hang any code relying on it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant