@Injected Property Wrapper in Swift: Clean Dependency Injection Without the Mess
Introduction
@Injected Property Wrapper in Swift: Clean Dependency Injection Without the Mess
Introduction
Dependency Injection (DI) is one of those concepts that every iOS developer eventually encounters. It sounds complex, but the core idea is simple: instead of a class creating its own dependencies, they are provided from the outside. This makes code modular, testable, and maintainable.
Swift’s [@propertyWrapper](http://twitter.com/propertyWrapper) feature, introduced in Swift 5.1, lets us build elegant DI solutions — and the [@Injected](http://twitter.com/Injected) pattern is one of the most powerful results.
The Problem: Constructor Injection Hell
Without DI tooling, you often end up with this:
class ProfileViewController: UIViewController {
let viewModel: ProfileViewModel
init(viewModel: ProfileViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
}
class ProfileViewModel {
let networkService: NetworkService
let analyticsService: AnalyticsService
init(networkService: NetworkService, analyticsService: AnalyticsService) {
self.networkService = networkService
self.analyticsService = analyticsService
}
}
Every layer has to pass dependencies down. It becomes unwieldy fast.
The Solution: @Injected Property Wrapper
Here’s how to build and use the [@Injected](http://twitter.com/Injected) pattern from scratch.
Step 1: Define Your Protocols
protocol NetworkService {
func fetchUser(id: String) async throws -> User
}
protocol AnalyticsService {
func track(event: String)
}
Step 2: Create a DI Container
final class DIContainer {
static let shared = DIContainer()
private var registry: [String: Any] = [:]
private init() {}
func register<T>(_ type: T.Type, factory: @escaping () -> T) {
registry[String(describing: type)] = factory
}
func resolve<T>(_ type: T.Type) -> T {
let key = String(describing: type)
guard let factory = registry[key] as? () -> T else {
fatalError("Dependency not registered: \(key)")
}
return factory()
}
}
Step 3: Build the @Injected Property Wrapper
@propertyWrapper
struct Injected<T> {
private var value: T
init() {
self.value = DIContainer.shared.resolve(T.self)
}
var wrappedValue: T {
get { value }
set { value = newValue }
}
}
Step 4: Register Dependencies at App Launch
@main
struct MyApp: App {
init() {
setupDependencies()
}
func setupDependencies() {
DIContainer.shared.register(NetworkService.self) {
RealNetworkService()
}
DIContainer.shared.register(AnalyticsService.self) {
FirebaseAnalyticsService()
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Step 5: Enjoy Clean Injection Everywhere
class ProfileViewModel: ObservableObject {
@Injected var networkService: NetworkService
@Injected var analyticsService: AnalyticsService
@Published var user: User?
func loadProfile(id: String) async {
analyticsService.track(event: "profile_view")
user = try? await networkService.fetchUser(id: id)
}
}
No constructor arguments. No prop drilling. Pure, readable code.
Bonus: Swapping for Tests
class MockNetworkService: NetworkService {
func fetchUser(id: String) async throws -> User {
return User(id: "mock", name: "Test User")
}
}
DIContainer.shared.register(NetworkService.self) {
MockNetworkService()
}
Swapping the entire implementation for testing takes one line.
Lazy Resolution Variant
If you want to defer resolution until the property is first accessed:
@propertyWrapper
struct LazyInjected<T> {
private var value: T?
var wrappedValue: T {
mutating get {
if value == nil {
value = DIContainer.shared.resolve(T.self)
}
return value!
}
}
}
Real-World Libraries Using This Pattern
- Resolver by Michael Long —
[@Injected](http://twitter.com/Injected)out of the box - Swinject — powerful container with scoping and assembly
- Factory — Swift-first, type-safe DI with
[@Injected](http://twitter.com/Injected)support
Conclusion
The [@Injected](http://twitter.com/Injected) property wrapper is a small but powerful abstraction. It separates the what from the how, keeps your classes clean, and makes testing a breeze. Whether you roll your own or use a library, this pattern is worth adding to your Swift toolkit.
Support me on Patreon! If you found this helpful, consider supporting me on Patreon. Your support helps me create more content like this! 🙏 https://www.patreon.com/c/Kanstantsin733
메타데이터
- post_id
- cf78a4e6e0ea
- slug
- injected-property-wrapper-in-swift-clean-dependency-injection-without-the-mess-cf78a4e6e0ea
- url
- https://medium.com/@kost9klinov/injected-property-wrapper-in-swift-clean-dependency-injection-without-the-mess-cf78a4e6e0ea
- canonical_url
- https://medium.com/@kost9klinov/injected-property-wrapper-in-swift-clean-dependency-injection-without-the-mess-cf78a4e6e0ea
- author_url
- https://medium.com/@kost9klinov
- status
- ok
- fetched_at
- 2026-09-02 06:49:20