Core philosophy
The iOS app is a dumb view layer. The backend owns all formatting, string construction, and business logic. The app’s job is to receive pre-formatted data and render it.What the iOS app does NOT do
- Format scores, overs, run rates, or player stats
- Decide what to show based on match state (e.g., innings break vs. live)
- Apply conditional logic to determine display layouts based on match phase
- Transform raw data into user-facing text
What the iOS app DOES do
- Render pre-formatted display states from the backend
- Manage UI navigation and presentation
- Handle device-level concerns (Live Activities, push tokens, permissions)
- Cache and poll for data
- Send user actions back to the backend
MVVM with @Observable and @Environment DI
The app uses MVVM powered by Swift’s@Observable macro (Observation framework), with dependency injection through @Environment.
- Real — hits the actual backend API
- Fake — returns canned data for previews and testing
ViewModels are
@Observable classes, not ObservableObject with @Published. This uses the newer Observation framework available from iOS 17, with backcompat handled where needed.Facade pattern: LiveActivityService
The most complex subsystem uses the facade pattern.LiveActivityService is a thin coordinator that composes three focused managers:
Views and other services only interact with the facade. The three managers are internal implementation details.
DataSource + ViewSubscriber
A generic pattern for any data that needs to be fetched, cached, and optionally polled.DataSource protocol
ViewSubscriber modifier
A SwiftUI view modifier that binds aDataSource to a view’s lifecycle.
Concrete data sources
Cache-first reads with stale checking
The app follows a cache-first strategy for all data:MatchCacheStoreindexes matches by date with TTL based on match state (live = short TTL, completed = long TTL)ImageCacheServiceuses a two-tier cache (memory + disk in app group container)ScheduleServiceuses a sliding-window cache for N days of schedule data
Cache-first means the UI never shows a loading spinner for data the user has already seen. Stale checks happen in the background and the UI updates reactively via
@Observable.Swift 6 actor isolation
The app uses Swift 6 strict concurrency. Key patterns:Data flow
1
Backend returns pre-formatted data
The API responds with display-ready models — formatted strings, ordered arrays, resolved image URLs.
2
Service layer fetches and caches
Protocol-based services make the API call, decode the response, and cache results (via
MatchCacheStore or service-local caches).3
ViewModel exposes state
@Observable view models hold the decoded data. SwiftUI views automatically re-render when properties change.4
Views render
SwiftUI views bind directly to view model properties. No transformation — just layout and styling.
5
User actions flow back
Taps, follows, and other interactions call service methods, which hit the backend API.