Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MRPF Universal Application Architecture

The app’s architecture is a modular, concurrency-safe design for a universal SwiftUI app using Swift 6 features (async/await, actors for isolation). It decouples UI from data/network/navigation/error logic, emphasizes non-blocking operations, and supports extensibility for new models/endpoints. Key updates incorporate reusable ErrorPublisher instances for both global and scoped error handling, addressing multi-window requirements on macOS (e.g., independent scanner errors per window, shared REST errors across all). AppContext centralizes initialization and propagation, simplifying complex setups across platforms, windows, previews, and backgrounds.

  • Decoupling and Data Flow: SwiftUI views (UX) interact only with high-level services/repositories/managers/publishers via @Environment and async calls (e.g., fetch(), startScan(), navigation triggers). Views use @Query for reactive local data from SwiftData, keeping them ignorant of network/caching/navigation/error details. Business logic is offloaded to actors/services/managers.
  • Concurrency and Performance: All network/storage ops are async and background-threaded. Actors (e.g., DataRepository, ScannerService) isolate mutable state to prevent data races. URLSession and WebSocket tasks run non-blockingly; SwiftData uses background contexts for writes to avoid UI stalls.
  • Navigation Management: NavigationManager (@Observable class or actor) is scene/window-scoped, handling navigation state (e.g., paths/stacks) independently per window on macOS. Created and injected per WindowGroup instance for multi-window isolation.
  • Network Clients:
    • APIClient (actor): Global, handles REST requests with error throwing.
    • WebSocketClient (actor): Global, manages WS connections, sending, and receiving as an AsyncStream. Shared across services.
  • Data Management:
    • DataRepository (actor): Global facade for REST/WS on models. Handles caching, fetches, subscriptions, and storage. Publishes errors to global ErrorPublisher.
    • ScannerService (@Observable actor): Scoped per window/instance (e.g., one per macOS window). Handles scan workflows, streams results, optional storage. Publishes errors to its own scoped ErrorPublisher.
  • Storage: SwiftData (ModelContainer) is app-global, initialized in AppContext. Models are @Model-conformed; services use background contexts.
  • Error Handling (Reusable/Decoupled): ErrorPublisher is a reusable @MainActor @Observable class managing a list of errors for multi-error support and selective dismissal:
    • var errors: [ErrorItem] = [] where ErrorItem = (id: UUID, error: Error, title: String).
    • Methods: publish(_ error: Error, title: String), dismiss(id: UUID), dismissAll().
    • Global Instance: For app-wide errors (e.g., REST API). Shared across all windows; injected app-wide.
    • Scoped Instances: For per-instance errors (e.g., scanner-specific). Each ScannerService has its own ErrorPublisher; injected to the relevant window/view subtree.
    • UX Display: Each window’s root view uses a custom ErrorOverlay or .alert that observes both global and local (scanner) publishers, combines their errors into a single list, and provides dismiss buttons (per error or all). This enables:
      • Global errors to appear in all windows.
      • Scoped errors to appear only in the affected window.
      • Concurrent errors shown together; dismiss one without affecting others.
  • Central Coordination with AppContext: AppContext (shared instance or singleton) initializes globals (e.g., ModelContainer, APIClient, DataRepository, WebSocketClient, global ErrorPublisher). It provides factories for scoped objects (e.g., createScannerService() -> ScannerService with new ErrorPublisher). Propagation:
    • App-wide globals via .environment in App body.
    • Scene/window-scoped (e.g., NavigationManager, ScannerService with scoped ErrorPublisher) created/injected in WindowGroup builder for per-window isolation on macOS.
    • Handles platform-specifics: Background wakeups for notifications, mock data for Xcode previews, window scene setup on macOS.
  • Extensibility: Add models via protocols. New services reuse clients/publishers. AppContext centralizes additions.
  • UX Bridge: Views trigger in Task {}, observe streams/status/@Query. No ViewModels. Navigation via scoped manager. Errors via combined overlay for global+scoped. This ensures scalability, multi-window correctness (independent scanner states/errors on macOS), and reusable error handling per 2026 SwiftUI practices.
graph TD
    subgraph "App Setup & Coordination"
        AppContext[AppContext Class
(init globals: ModelContainer, APIClient, Repository, WebSocketClient, global ErrorPublisher; factories for scoped: ScannerService w/ scoped EP, NavigationManager; handles previews/background)]
        AppContext -->|app-wide injection
(e.g., repository, global EP)| App[App Struct]
        App -->|scene-specific creation/injection
(e.g., new NavManager, new ScannerService w/ scoped EP per window)| WindowScene[WindowGroup/Scene
(multi-window on macOS)]
        AppContext -.->|handles previews/background/windowscene| PreviewsBackground[Previews, Background Wakeups, Window Scenes]
    end

    subgraph "UX Layer"
        WindowScene -->|binds scoped: NavManager, ScannerService, scoped EP| Views[SwiftUI Views (UX)]
        Views -->|async calls (e.g., fetch())| DataRepository
        Views -->|async calls (e.g., startScan())| ScannerService
        Views -->|observes @Query| SwiftData[SwiftData (ModelContainer)]
        Views -->|observes status/streams| ScannerService
        Views -->|triggers navigation| NavigationManager[NavigationManager
(scoped per-window)]
        RootViewPerWindow[Root View per Window] -->|custom ErrorOverlay: combines & shows lists from global + scoped EPs
(dismiss one/all)| ErrorPublisherGlobal[Global ErrorPublisher
(app-wide errors, e.g., REST)]
        RootViewPerWindow -->|custom ErrorOverlay| ErrorPublisherScoped[Scoped ErrorPublisher
(per-scanner/window errors)]
    end

    subgraph "Service Layer (Actors/Managers)"
        DataRepository[DataRepository Actor
(global; caching, storage)] -->|uses for REST| APIClient[APIClient Actor
(global)]
        DataRepository -->|uses for subscriptions| WebSocketClient[WebSocketClient Actor
(global)]
        ScannerService[ScannerService @Observable Actor
(scoped per-window; scan logic)] -->|uses for WS| WebSocketClient
    end

    subgraph "Network/Storage"
        APIClient -.->|network I/O| RemoteREST[Remote REST API]
        WebSocketClient -.->|network I/O| RemoteWS[Remote WS Service]
        DataRepository -->|background ops| SwiftData
        ScannerService -->|optional background store| SwiftData
    end

    subgraph "Error Handling (Reusable)"
        DataRepository -->|publishes (MainActor.run)| ErrorPublisherGlobal
        ScannerService -->|publishes| ErrorPublisherScoped
        APIClient -->|throws| DataRepository
        WebSocketClient -->|throws| ScannerService
    end

    style AppContext fill:#f9f,stroke:#333
    style App fill:#f9f,stroke:#333
    style WindowScene fill:#f9f,stroke:#333
    style Views fill:#bbf,stroke:#333
    style NavigationManager fill:#fd9,stroke:#333
    style DataRepository fill:#fd9,stroke:#333
    style ScannerService fill:#fd9,stroke:#333
    style APIClient fill:#dfd,stroke:#333
    style WebSocketClient fill:#dfd,stroke:#333
    style SwiftData fill:#9f9,stroke:#333
    style ErrorPublisherGlobal fill:#f99,stroke:#333
    style ErrorPublisherScoped fill:#f99,stroke:#333
    style RemoteREST fill:#ccc,stroke:#333
    style RemoteWS fill:#ccc,stroke:#333
    style PreviewsBackground fill:#ccc,stroke:#333