DEV Community

Hugo Granja
Hugo Granja

Posted on • Edited on

Creating a simple dependency injection framework in Swift [Part 3]: Auto-registration

Part 2

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)
    )
}
Enter fullscreen mode Exit fullscreen mode

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))
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Part 4 - Dynamic arguments

Top comments (0)