Skip to main content

Architecture Pattern

r2Vault follows a modern MVVM (Model-View-ViewModel) architecture using Swift 6’s @Observable macro for reactive state management.

Key Architectural Principles

Unidirectional Data Flow

Views observe ViewModels, ViewModels coordinate Services, Services return data

Separation of Concerns

Models, Services, ViewModels, and Views are cleanly separated

Reactive UI

SwiftUI views automatically update when observable state changes

Async/Await First

All network and file operations use structured concurrency

MVVM Implementation

Observable ViewModels

r2Vault uses Swift 6’s @Observable macro for automatic change tracking:
ViewModels/AppViewModel.swift
The @Observable macro eliminates the need for manual @Published properties and ObservableObject conformance, providing better performance and cleaner code.

SwiftUI + AppKit Hybrid

r2Vault combines SwiftUI for the main interface with AppKit for menu bar functionality:
Services/MenuBarManager.swift
Why the hybrid approach?SwiftUI’s menu bar support is limited. Using NSStatusItem and NSPopover provides:
  • Persistent menu bar icon
  • Control over popover behavior (.applicationDefined)
  • Better window management

Concurrency Model

Structured Concurrency with async/await

All asynchronous operations use Swift’s modern concurrency:
ViewModels/AppViewModel.swift

Parallel Uploads with TaskGroup

Multiple files upload concurrently using withTaskGroup:
ViewModels/AppViewModel.swift

Concurrent Deletion with TaskGroup

Bulk deletions happen in parallel:
ViewModels/AppViewModel.swift
Actor isolation: Services are marked nonisolated enum to allow calling from any actor context. ViewModels run on @MainActor for UI updates.

State Management

Single Source of Truth

AppViewModel is the single source of truth, created once at app launch:
FiaxeApp.swift

Environment Injection

The view model flows down the view hierarchy via SwiftUI’s environment:
ContentView.swift
Use @Bindable to create two-way bindings with @Observable models in SwiftUI views.

Per-Task Observable State

Each upload task is its own observable object:
Models/UploadTask.swift

Data Flow

Example: File Upload Flow

  1. User drops filesContentView calls viewModel.handleDroppedURLs()
  2. ViewModel creates tasks → Builds FileUploadTask objects, adds to queue
  3. ViewModel spawns upload → Calls uploadPendingTasks() with TaskGroup
  4. Service performs uploadR2UploadService.upload() signs request, uploads via URLSession
  5. Progress updates → Delegate calls @MainActor closure, updates task progress
  6. View reacts → SwiftUI observes task changes, updates progress bar
  7. Completion → Task status changes to .completed, view shows success

Thread Safety

MainActor for UI

All view models and UI state run on the main actor:

Actor for Caching

The thumbnail cache is an actor for safe concurrent access:
Services/ThumbnailCache.swift

Sendable Types

All data types crossing actor boundaries are Sendable:
Models/R2Credentials.swift

Next Steps

Project Structure

Explore the directory layout and module organization

Tech Stack

Learn about the frameworks and dependencies used