Closed iMattin closed 2 years ago
Hello!
I don't think this needs a dedicated feature in the library.
// Core (low level) module - begin
protocol Interceptor {
func intercept()
}
// Core (low level) module - end
// Module A begin
class A_Interceptor: Interceptor {
func intercept() {
print("A")
}
}
// Module A end
// Module B begin
class B_Interceptor: Interceptor {
func intercept() {
print("B")
}
}
// Module B end
// Module C begin
class C_Interceptor: Interceptor {
func intercept() {
print("C")
}
}
// Module C begin
// ... registration in the toplevel project - begin
container.register([Interceptor].self) {
[A.A_Interceptor(), B.B_Interceptor(), C.C_Interceptor()]
}
// ... registration in the toplevel project - end
// USAGE
// In any module that has a dependency on "Core" module
class MyService {
@Injected private var interceptors: [Interceptor]
func intercept() {
interceptors.forEach { $0.intercept() }
}
}
Calling intercept()
on a MyService
instance prints the following as expected:
A
B
C
If you want distinguish multiple array registrations, you can use the name
paramater.
I think your problem all comes down to correct project setup. You can have implementing classes in various modules (such as "A", "B" and "C"), and make a combined registration in the toplevel project (which is typically the application target itself).
If for one protocol like NetworkServiceProtocol, we have different implementation like NetworkServiceA and NetworkServiceB. And in different use case, we want to inject different ones. How does Resolver framework fullfill this?
An option is to use different resolution types where needed.
// Registration begin
container.register { NetworkServiceA() }
.implements(NetworkServiceProtocol.self)
container.register { NetworkServiceB() }
.implements(NetworkServiceProtocol.self)
container.register(NetworkServiceProtocol].self) { container in
return [container.resolve(NetworkServiceA.self), container.resolve(NetworkServiceB.self)]
}
// Registration end
class MyService1 {
// [NetworkServiceA instance, NetworkServiceB instance] resolved
@Injected private var allServices: [NetworkServiceProtocol]
}
class MyServiceA {
// NetworkServiceA resolved
@Injected private var aService: NetworkServiceA
}
class MyServiceB {
// NetworkServiceB resolved
@Injected private var bService: NetworkServiceB
}
Another way is to use services names.
I think I need to reject this, for a couple of reasons.
First and foremost is that it's relatively easy to accomplish without changing Resolver. Given...
protocol Interceptor {
func intecept()
}
class MyInterceptorA: Interceptor {
func intecept() {}
}
class MyInterceptorB: Interceptor {
func intecept() {}
}
You can do....
extension Resolver {
static func registerInterceptors1() {
register { MyInterceptorA() }
register { MyInterceptorB() }
register([Interceptor].self) { [resolve(MyInterceptorA.self), resolve(MyInterceptorB.self)] }
}
}
Or, if you prefer, using name spaces....
extension Resolver.Name {
static let iA = Self("iA")
static let iB = Self("iB")
}
extension Resolver {
static func registerInterceptors2() {
register(name: .iA) { MyInterceptorA() as Interceptor }
register(name: .iB) { MyInterceptorB() as Interceptor }
register([Interceptor].self) { [resolve(name: .iA), resolve(name: .iB)] }
}
}
Either way, it's resolved as usual.
class Demo {
@Injected var interceptors: [Interceptor]
}
The second reason is that, as is, the code only works correctly with a single container. It doesn't check every child container and see what Interceptors have of the same type been registered in each, nor would this approach honor mock containers for testing.
The final reason is that I've done this sort of thing before and the basic problem is that one usually needs their interceptors in a specific order (maybe retry first, then error handling, then logging, etc.), and that would be almost impossible to accomplish if various modules each registered their own interceptors in their own order.
As shown, with...
register([Interceptor].self) { [resolve(name: .iA), resolve(name: .iB)] }
You can explicitly control the order in which they're resolved and returned in the array.
Appreciate the work and the fact that you're using Resolver.
This is a fun approach with names where anyone can add a bunch of named interceptors and the final resolution doesn't need to know the exact types...
extension Resolver {
static var interceptors: [Resolver.Name] = []
static func registerInterceptors3() {
register(name: .iA) { MyInterceptorA() as Interceptor }
interceptors.append(.iA)
register(name: .iB) { MyInterceptorB() as Interceptor }
interceptors.append(.iB)
register([Interceptor].self) {
Resolver.interceptors.map { resolve(name: $0) }
}
}
}
Thanks for your replies. Your solution was complete and convincing.
Introduction
After working with this library for a while, I noticed a shortcoming in it: Multi Injection Suppose we need an injectable array of a specific type, If this dependency is already registered somewhere as an array once, everything will work fine. But what if we want the elements of this array to be registered in different parts of the code?
Explain the scenario
In this scenario we have an injectable array of interceptors in the
NetworkManager
class which is defined in our network layer:We need to register various interceptors in different modules and we expect these interceptors to be injected as an array to our
NetworkManager
Let me continue in the code...