This guide covers how StarterAppKit uses the Factory dependency injection container to manage and resolve dependencies throughout the application.
The main point of integration is in StarterApp+Container.swift
, located at:
iosStarterApp/Helpers/StarterApp+Container.swift
Within this file, the container is implemented as a singleton through the SharedContainer
protocol provided by Factory.
The container class provides a shared, globally accessible point to define and resolve dependencies. For example:
final class StarterAppContainer: SharedContainer { let manager = ContainerManager() static var shared = StarterAppContainer() }
All dependencies are defined as computed properties within an extension of StarterAppContainer
. Each property returns a Factory
type that describes how to create and optionally cache the dependency instance.
extension StarterAppContainer { var myNewService: Factory<MyNewService> { self { MyNewService() }.cached } }
self { MyNewService() }
defines how to create a MyNewService
instance..cached
ensures the created instance is retained for reuse until memory pressure dictates otherwiseSome services may need configuration or parameters:
extension StarterAppContainer { var authenticatedService: Factory<AuthService> { self { AuthService(apiKey: AppConstants.apiKey) }.cached } }
This example injects a fixed API key from AppConstants
, ensuring each resolved AuthService
is consistently configured.
Factory supports different storage strategies:
.cached
: A single instance is created and reused; it may be released under memory pressure..singleton
: A single permanent instance is created and never released during the app’s lifecycle..unique
: A fresh instance is created each time the property is accessed.To consume defined dependencies, rely on @Injected
property wrappers within classes like view models:
import Factory class MyViewModel: ObservableObject { @Injected(\StarterAppContainer.networkManager) private var networkManager @Injected(\StarterAppContainer.dataStore) private var dataStore func fetchData() { // Use networkManager and dataStore here } }
This approach keeps your code cleaner and reduces the need for manual initialization in every class that needs a dependency.
var networkManager: Factory<NetworkManager> { self { NetworkManager() }.cached }
This sets up a reusable NetworkManager
instance that’s retained as long as memory conditions allow.
var deeplinkManager: Factory<DeepLinkManager> { self { DeepLinkManager(configuration: AppConstants.deepLinkConfig) }.singleton }
This ensures DeepLinkManager
persists throughout the app’s lifecycle, ideal for maintaining state or event registration.
.cached
: It offers a good balance of reusability and memory management..singleton
for Stateful Managers: Keep services that need to maintain long-term state alive for the whole app session..unique
Sparingly: Only use when you must ensure a fresh instance (e.g., transient operations).Here’s a step-by-step example to integrate a new analytics service:
class AnalyticsService { func logEvent(_ name: String) { // Implementation } }
extension StarterAppContainer { var analyticsService: Factory<AnalyticsService> { self { AnalyticsService() }.singleton } }
class HomeViewModel: ObservableObject { @Injected(\StarterAppContainer.analyticsService) private var analytics func onButtonTap() { analytics.logEvent("button_tapped") } }
By following these guidelines and best practices, you can maintain a clean, organized, and testable dependency injection setup in StarterAppKit.