Creating a simple dependency injection framework in Swift [Part 3]: Auto-registration
Hugo Granja
Posted on November 5, 2024
Registering services with multiple dependencies can quickly become verbose:
container.register(One.self) { _ in
return OneImpl()
}
container.register(Two.self) { _ in
return TwoImpl()
}
container.register(Three.self) { _ in
return ThreeImpl()
}
container.register(Example.self) { container in
return ExampleImpl(
first: container.resolve(One.self),
second: container.resolve(Two.self),
third: container.resolve(Three.self)
)
}
Each service must be individually registered, and each dependency resolved manually. To streamline this, we can create an utility method that automatically resolves all dependencies required by an initializer. This is possible by using parameter packs, which enable handling an arbitrary number of dependencies generically.
public func autoRegister<T, each D>(
_ type: T.Type,
lifetime: Lifetime = .transient,
using initializer: @escaping (repeat each D) -> T
) {
register(type, lifetime: lifetime) { r in
initializer(repeat r.resolve((each D).self))
}
}
This allows us to transform our previous example into:
container.autoRegister(One.self, using: OneImpl.init)
container.autoRegister(Two.self, using: TwoImpl.init)
container.autoRegister(Three.self, using: ThreeImpl.init)
container.autoRegister(Example.self, using: Example.init)
Now, each dependency is registered concisely, without repetitive resolve calls. This method greatly reduces boilerplate and makes the code easier to read and maintain, especially as the number of dependencies grows.
In the next post, we will see how to provide arguments only available at runtime when resolving dependencies.
Posted on November 5, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.