CFA Reference: Application Launch
Formal CFA AI Toolkit for iOS Documentation · Reference 14 of 26
CFA starts app-wide work at the earliest launch callback and screen work in each screen's .task. Neither blocks the first frame, and neither promises that loading has finished.
Definitions
- Launch callback: The app delegate's didFinishLaunchingWithOptions, attached with @UIApplicationDelegateAdaptor.
- applicationDidFinishLaunching(): AppModel's synchronous method that checks a Boolean guard and calls each Feature's startup method.
- Stable root: App.body declares one root View and app-wide presentation modifiers only.
- Screen .task: SwiftUI work started before a View appears, owned by that screen.
Rules
- The app delegate directly calls AppModel's synchronous applicationDidFinishLaunching() (rule 9).
- AppModel uses one Boolean guard and synchronously invokes Feature startup methods; Features own their async work.
- Keep synchronous startup brief; do not block on disk or network I/O.
- No View .task performs app launch, and no App, delegate or View stores an AppModel reference (rule 16).
- App.body has a stable root with no startup if/switch, storage gating, database loading or feature error display (rule 17).
- Database availability never gates the interface, including root wrappers and blocking overlays (rule 18).
- Never introduce a timed splash or storage gate.
Decision rules
- When launch and a screen both request the same refresh, let them await the Feature's one in-flight task.
- When storage fails to open, let the Feature's request fail and show the error on that feature's screen.
- When you need app-wide refresh on launch, add a Feature startup call; do not move launch into RootTabView.
- When the user reopens the app, rely on the guard so launch work is not started twice.
Example
The compiled AppModel shows the guarded, synchronous launch method. The Feature it calls returns immediately after creating its task.
Compiled example (excerpt)
import Foundation
import Observation
// MARK: Composition root
@MainActor
@Observable
final class AppModel {
static let shared = live()
let habitsFeature: HabitsFeature
let settingsFeature: SettingsFeature
private var didFinishLaunching: Bool
init(habitsFeature: HabitsFeature, settingsFeature: SettingsFeature) {
self.habitsFeature = habitsFeature
self.settingsFeature = settingsFeature
didFinishLaunching = false
}
static func live() -> AppModel {
let habitsRepository = LocalHabitsRepository()
return AppModel(
habitsFeature: HabitsFeature(repository: habitsRepository),
settingsFeature: SettingsFeature(defaults: .standard)
)
}
func applicationDidFinishLaunching() {
guard !didFinishLaunching else { return }
didFinishLaunching = true
habitsFeature.startLoading()
}
}Compiled with Swift 6 language mode and warnings treated as errors: type-checked on Swift 6.2 and built on Swift 6.4 (Linux toolchains, Foundation and Observation only).
Common mistakes
- Branching in App.body on whether the database has loaded.
- Awaiting a network request in the app delegate.
- Using RootTabView's .task as the app's launch mechanism.
- Showing a launch error screen instead of a feature-level, retryable error.
- Adding a delay so a splash screen is visible.
