Hello Swift community ![]()
Iād like to propose first-class dependency injection support in Swift using macros and attributes.
Summary
This proposal introduces:
@Injectfor property injection@AutoInjectfor automatic initializer generation@DependencyContainerfor defining dependencies
The goal is to reduce boilerplate, improve testability, and provide a standardized DI approach in Swift.
Motivation (Short)
Dependency Injection is widely used but currently:
- Requires boilerplate-heavy code
- Lacks standardization
- Relies on third-party frameworks
With macros now available, Swift can support DI natively in a type-safe and compiler-assisted way.
Detailed Proposal
Swift Evolution Pitch Draft
Title
First-Class Dependency Injection Support Using Macros and Attributes
Author
Pramod Kumar (iOS Developer)
Status
Pitch
Introduction
Dependency Injection (DI) is a fundamental design pattern used in modern Swift applications to improve modularity, testability, and maintainability.
Despite its importance, Swift currently lacks first-class language or standard library support for dependency injection, leading developers to rely on:
-
Manual constructor/property injection
-
Custom service locators
-
Third-party frameworks (e.g., Swinject, Resolver)
With the introduction of macros and compile-time metaprogramming, Swift is now well-positioned to provide a native, lightweight, and type-safe DI mechanism.
This proposal introduces first-class dependency injection support using macros and attributes, enabling a standardized and compiler-assisted approach.
Motivation
In real-world Swift applications (especially at scale), DI becomes essential for:
-
Decoupling components
-
Enabling unit testing
-
Managing object lifecycles
-
Supporting modular architectures (MVVM, Clean Architecture, etc.)
Current Challenges
1. Boilerplate-heavy code
class UserViewModel {
let apiService: APIService
init(apiService: APIService) {
self.apiService = apiService
}
}
2. Inconsistent patterns across teams
-
Constructor injection
-
Property injection
-
Singleton usage (anti-pattern)
3. Lack of compile-time guarantees
- Missing dependencies only fail at runtime in many DI frameworks
4. Over-reliance on third-party libraries
-
Adds maintenance overhead
-
Fragmented ecosystem
Proposed Solution
Introduce compiler-supported dependency injection using macros and attributes, with the following core features:
1. Property Injection
@Inject var apiService: APIService
-
Automatically resolves dependency from a registered container
-
Eliminates manual wiring
2. Constructor Injection via Macro
@AutoInject
class UserViewModel {
let apiService: APIService
let logger: Logger
}
Compiler generates:
init(apiService: APIService, logger: Logger) {
self.apiService = apiService
self.logger = logger
}
3. Dependency Container Definition
@DependencyContainer
struct AppContainer {
let apiService = APIService()
let logger = Logger()
}
-
Acts as a central registry
-
Compile-time validation of dependencies
4. Scoped Resolution
@Inject(scope: .singleton) var apiService: APIService
Possible scopes:
-
.singleton -
.transient -
.scoped
5. Testability Support
@Inject(mock: MockAPIService.self)
var apiService: APIService
-
Enables seamless mocking in tests
-
No need for separate configuration
Detailed Design
Macro Responsibilities
@Inject
-
Expands into:
-
Backing storage
-
Resolution logic
-
-
Ensures type-safe resolution
@AutoInject
-
Synthesizes initializer
-
Validates all stored properties are injectable
@DependencyContainer
-
Registers dependencies
-
Builds a compile-time dependency graph
Compile-Time Safety
The compiler should:
-
Validate all dependencies exist
-
Detect circular dependencies
-
Ensure type compatibility
Example End-to-End Usage
@DependencyContainer
struct AppContainer {
let apiService = APIService()
}
@AutoInject
class UserViewModel {
@Inject var apiService: APIService
}
Benefits
1. Reduced Boilerplate
- Eliminates repetitive initializers
2. Standardization
- One consistent DI approach across Swift ecosystem
3. Compile-Time Safety
- Fail early instead of runtime crashes
4. Improved Testability
- Built-in mocking support
5. Alignment with Swift Evolution
- Leverages macros (modern Swift direction)
Alternatives Considered
1. Status Quo (Manual DI)
- Verbose and error-prone
2. Third-Party Libraries
-
Fragmented ecosystem
-
No compiler guarantees
3. Service Locator Pattern
-
Hides dependencies
-
Considered anti-pattern
Impact on Existing Code
-
Fully opt-in feature
-
No breaking changes
-
Existing DI approaches continue to work
Future Directions
-
Integration with SwiftUI Environment
-
Tooling support (dependency graphs visualization)
-
IDE diagnostics for dependency issues
-
Integration with testing frameworks
Open Questions
-
Should dependency containers be global or scoped per module?
-
How should lifecycle management be handled in async contexts?
-
Should SwiftPM integrate DI container configuration?
Conclusion
This proposal aims to bring first-class, type-safe, and compiler-assisted dependency injection to Swift, reducing boilerplate, improving safety, and unifying development practices across the ecosystem.
With macros now available, this is the right time to introduce a modern DI system natively in Swift.