Skip to main content

Overview

Every service in the iOS app follows the same pattern:
  1. A Protocol defines the interface
  2. A Real implementation (e.g., CognitoAuthService) talks to the backend
  3. A Fake implementation returns canned data for previews and tests
  4. All services are @Observable and injected via @Environment

AppBootstrap

Responsibility: Cold-start phase gating. AppBootstrap orchestrates the app launch sequence, ensuring services initialize in the correct order before the UI becomes interactive.
If any phase fails (e.g., maintenance mode detected), AppBootstrap halts and shows the appropriate blocking screen. The user never sees a partially initialized app.

AuthService

Protocol: AuthService Real implementation: CognitoAuthService Handles all Cognito-based authentication flows.

Admin detection

Admin status is determined by checking the JWT token’s cognito:groups claim. If the user belongs to the admin group, AppUser.isAdmin is true.
Admin detection is read-only on the client. The backend enforces admin permissions independently via its own JWT validation.

LiveActivityService (facade)

Protocol: LiveActivityService Real implementation: RealLiveActivityService The facade composes three managers and exposes a unified API to the rest of the app. Views and other services interact with LiveActivityService — never with the managers directly.
The LiveActivityService facade is the most stateful part of the app. See the Live Activities page for the full lifecycle, token management, and push-to-start flow.

NotificationService

Protocol: NotificationService Real implementation: RealNotificationService Handles push notification permissions and device registration with dual token management.

Dual tokens

The app manages two distinct APNs tokens: Both tokens are registered with the backend on launch and whenever they rotate.

ScheduleService

Protocol: ScheduleService Real implementation: RealScheduleService Fetches and caches match schedules.

DataSource protocol + ViewSubscriber modifier

A generic pattern for data that needs to be fetched, cached, and optionally polled.

DataSource protocol

ViewSubscriber modifier

A SwiftUI view modifier that binds a DataSource to a view’s lifecycle.
ViewSubscriber eliminates the need for each view to manually manage polling timers and lifecycle events. Attach it once and the data source handles the rest.

LiveMatchesDataSource

ScheduleDataSource


MatchCacheStore

Three-layer cache for match data with intelligent invalidation.

Cache layers

Hydration on launch

loadFromDisk() is called during AppBootstrap to hydrate the in-memory cache from disk before other boot tasks run. This means the app can render cached schedules immediately, even before the first network response arrives.

TTL by state

Swift 6 gotcha: Synthesized Codable conformance on structs is main-actor-isolated in Swift 6. The cache model structs require explicit nonisolated init(from:) and nonisolated encode(to:) implementations to allow background encoding/decoding by DiskCacheStore. Without this, the compiler will emit a concurrency error when disk I/O runs off the main actor.

ImageCacheService

Protocol: ImageCacheService Real implementation: RealImageCacheService Two-tier image cache for team logos and assets.
The disk cache uses the app group container so both the main app and the widget target can access cached images. This avoids duplicate downloads for team logos shown in Live Activities.

AppConfigService

Protocol: AppConfigService Real implementation: RealAppConfigService Checks app configuration on launch.
The version check runs on every app launch. The backend controls the minimum version, so you can force-update users without an App Store review cycle.

AdminService

Protocol: AdminService Real implementation: RealAdminService Debug-only service for controlling fake mode.
This service is only available in debug builds. It connects to fake-api.sightscreen.app for testing.

AppLogger

Unified logging across three destinations.
Use AppLogger for all logging. Never use print() or raw os_log calls. AppLogger ensures every log event reaches all three destinations with consistent formatting.

DI wiring

All services are wired at the app entry point and injected into the SwiftUI environment:
For SwiftUI previews, swap in fakes: