I would also advise in using the main App's init method for this one, as it seems safe to use (any objections?).
What I usually do, that might be useful to share, is to have a couple of utility types, combined with the Builder pattern.
/// An abstraction for a predefined set of functionality, /// aimed to be ran once, at app startup. protocol StartupProcess { func run() } /// A convenience type used for running StartupProcesses. /// Uses the Builder pattern for some coding eye candy. final class StartupProcessService { init() { } /// Executes the passed-in StartupProcess by running it's "run()" method. /// - Parameter process: A StartupProcess instance, to be initiated. /// - Returns: Returns "self", as a means to chain invocations of StartupProcess instances. @discardableResult func execute(process: any StartupProcess) -> StartupProcessService { process.run() return self } }
and then we have some processes
struct CrashlyticsProcess: StartupProcess { func run() { // Do stuff, like SDK initialization, etc. } } struct FirebaseProcess: StartupProcess { func run() { // Do stuff, like SDK initialization, etc. } } struct AppearanceCustomizationProcess: StartupProcess { func run() { // Do stuff, like SDK initialization, etc. } }
and finally, running them
@main struct TheApp: App { init() { initiateStartupProcesses() } var body: some Scene { WindowGroup { ContentView() } } } private extension TheApp { func initiateStartupProcesses() { StartupProcessService() .execute(process: ExampleProcess()) .execute(process: FirebaseProcess()) .execute(process: AppearanceCustomizationProcess) } }
Seems quite nice and super clean.