Setting up Dependency Injection with Factory in iOS (SwiftUI)
Factory — DI library by Michael Long, written specifically for Swift and SwiftUI. Unlike Swinject, it doesn't use string-based registration and doesn't throw runtime crash on unregistered dependency. Everything resolved at type level in compile time, configuration errors visible immediately in Xcode.
Why Factory Instead of Swinject for SwiftUI
Swinject created for UIKit and Objective-C runtime. In SwiftUI project it works, but @Environment and @StateObject create competition for object lifecycle management — questions arise about ViewModel ownership. Factory uses @Injected property wrapper and integrates with SwiftUI dependency approach organically.
Setup
Dependencies registered via extension on Container:
import Factory
extension Container {
var apiClient: Factory<APIClient> {
Factory(self) { DefaultAPIClient() }.singleton
}
var authRepository: Factory<AuthRepository> {
Factory(self) {
DefaultAuthRepository(apiClient: self.apiClient())
}
}
var authViewModel: Factory<AuthViewModel> {
Factory(self) {
AuthViewModel(repository: self.authRepository())
}
}
}
Use in View:
struct LoginView: View {
@StateObject private var viewModel = Container.shared.authViewModel()
var body: some View {
// ...
}
}
Or via @Injected for services without UI:
class PaymentService {
@Injected(\.apiClient) private var api
}
Scopes and Lifecycle Management
Factory supports four scopes:
| Scope | Behavior |
|---|---|
.singleton |
Single instance for entire app |
.cached |
Single instance until explicit reset (Container.shared.reset()) |
.shared |
Lives while at least one strong reference exists |
unique (default) |
New object on each access |
.shared — weak singleton analog: when all Views using ViewModel destroyed, object freed. Useful for screens with heavy state you don't need to keep forever.
Testing
Factory's main advantage — easy override for tests:
// In setUp() of test case:
Container.shared.authRepository.register {
MockAuthRepository(shouldSucceed: true)
}
// Reset after test:
Container.shared.reset()
No need separate test container, no mocks via protocol substitution in production code — registration simply rewritten for test duration.
Typical Issue: ViewModel Recreation
@StateObject creates object once on first View render. But if View recreates (e.g., Tab switch), @StateObject persists. Problem arises when using @ObservedObject instead of @StateObject with Factory — then ViewModel recreates on every render, losing state.
Rule: use @StateObject for creating ViewModel via Factory at View level, @ObservedObject — only for ViewModel passed externally via init.
Work Included
-
Containersetup with all app layers registration - Proper Scope for each dependency type
-
@Injectedfor service layer,@StateObject+ Factory for ViewModel - Dependency override for Unit/UI tests
- Documentation for extending container with new dependencies
Timeline
2–3 days for typical SwiftUI project. Includes refactoring existing code to DI approach if project already written without container. Cost calculated individually.







