The @Injected property wrapper is WeaveDI's flagship dependency injection solution, inspired by TCA's @Dependency but optimized for WeaveDI. It provides type-safe, compile-time checked dependency injection with zero configuration overhead.
@Injected represents a modern approach to dependency injection in Swift, combining the best practices from The Composable Architecture (TCA) with WeaveDI's powerful container management. It eliminates the need for optional handling while maintaining compile-time safety and provides both KeyPath-based and Type-based access patterns.
Key Benefits:
- ✅ Type-Safe: Compile-time type checking
- ✅ TCA-Style: Familiar API for TCA developers
- ✅ Flexible: Supports both KeyPath and Type-based access
- ✅ Immutable: No
mutating getrequired - ✅ Testable: Easy to override in tests
struct APIClientKey: InjectedKey {
static let liveValue: APIClient = APIClientImpl()
static let testValue: APIClient = MockAPIClient()
}extension InjectedValues {
var apiClient: APIClient {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}struct MyFeature: Reducer {
@Injected(\.apiClient) var apiClient
func reduce(into state: inout State, action: Action) -> Effect<Action> {
.run { send in
let data = try await apiClient.fetchData()
await send(.dataLoaded(data))
}
}
}You can also use @Injected with types directly:
extension ExchangeUseCaseImpl: InjectedKey {
public static var liveValue: ExchangeRateInterface {
let repository = UnifiedDI.register(ExchangeRateInterface.self) {
ExchangeRepositoryImpl()
}
return ExchangeUseCaseImpl(repository: repository)
}
}
struct CurrencyFeature: Reducer {
@Injected(ExchangeUseCaseImpl.self) var exchangeUseCase
}Override dependencies in tests using withInjectedValues:
func testFetchData() async {
await withInjectedValues { values in
values.apiClient = MockAPIClient()
} operation: {
let feature = MyFeature()
// Test with mock
}
}| Feature | @Injected (Deprecated) | @Injected (New) |
|---|---|---|
| Type Safety | ❌ Optional-based | ✅ Compile-time |
| TCA Style | ❌ Different | ✅ Familiar |
| KeyPath | ✅ Supported | ✅ Supported |
| Type Access | ❌ No | ✅ Supported |
| Immutable | ❌ Needs mutating | ✅ Non-mutating |
| Testing | ✅ Built-in |
// ❌ Old
@Injected var repository: UserRepository?
// ✅ New
@Injected(\.repository) var repository// ❌ Old
let repository = UnifiedDI.requireResolve(UserRepository.self)
// ✅ New
@Injected(\.repository) var repository- Prefer KeyPath access - Better discoverability
- Use Type access - Quick prototyping
- Always define testValue - Easier testing
- Keep InjectedKey extensions close - Near type definition
protocol ExchangeRateInterface: Sendable {
func getExchangeRates(currency: String) async throws -> ExchangeRates?
}
extension ExchangeUseCaseImpl: InjectedKey {
public static var liveValue: ExchangeRateInterface {
// Return protocol implementation
ExchangeUseCaseImpl(repository: ...)
}
}
extension InjectedValues {
var exchangeUseCase: ExchangeRateInterface {
get { self[ExchangeUseCaseImpl.self] }
set { self[ExchangeUseCaseImpl.self] = newValue }
}
}// 1. Define InjectedKey
extension ExchangeUseCaseImpl: InjectedKey {
public static var liveValue: ExchangeRateInterface {
let repository = UnifiedDI.register(ExchangeRateInterface.self) {
ExchangeRepositoryImpl()
}
return ExchangeUseCaseImpl(repository: repository)
}
}
// 2. Extend InjectedValues
public extension InjectedValues {
var exchangeUseCase: ExchangeRateInterface {
get { self[ExchangeUseCaseImpl.self] }
set { self[ExchangeUseCaseImpl.self] = newValue }
}
}
// 3. Use in Reducer
struct CurrencyFeature: Reducer {
@Injected(\.exchangeUseCase) var exchangeUseCase
func reduce(into state: inout State, action: Action) -> Effect<Action> {
case .fetchRates(let currency):
return .run { send in
let rates = try await exchangeUseCase.getExchangeRates(currency: currency)
await send(.ratesLoaded(rates))
}
}
}- @Factory - For creating new instances each time
- Property Wrappers Guide - Complete property wrapper documentation
- Testing Guide - Advanced testing patterns