[Pitch] First-Class Dependency Injection Support Using Macros and Attributes

Hello Swift community :waving_hand:

I’d like to propose first-class dependency injection support in Swift using macros and attributes.

Summary

This proposal introduces:

  • @Inject for property injection
  • @AutoInject for automatic initializer generation
  • @DependencyContainer for 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

:puzzle_piece: Swift Evolution Pitch Draft

:pushpin: Title

First-Class Dependency Injection Support Using Macros and Attributes


:technologist: Author

Pramod Kumar (iOS Developer)


:date: Status

Pitch


:puzzle_piece: 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.


:puzzle_piece: 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


:light_bulb: 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


:brain: 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
}


:bullseye: 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)

:balance_scale: 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


:counterclockwise_arrows_button: Impact on Existing Code

  • Fully opt-in feature

  • No breaking changes

  • Existing DI approaches continue to work


:rocket: Future Directions

  • Integration with SwiftUI Environment

  • Tooling support (dependency graphs visualization)

  • IDE diagnostics for dependency issues

  • Integration with testing frameworks


:red_question_mark: Open Questions

  1. Should dependency containers be global or scoped per module?

  2. How should lifecycle management be handled in async contexts?

  3. Should SwiftPM integrate DI container configuration?


:test_tube: 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.

1 Like