hmlongco / Resolver

Swift Ultralight Dependency Injection / Service Locator framework
MIT License
2.14k stars 189 forks source link

Reading a value from a registered service inside registerAllServices #126

Closed murad1981 closed 3 years ago

murad1981 commented 3 years ago

Is there a way to read a value from a registered service inside registerAllServices, in the following example:

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        register {
             AppConfig()
        }

        register(String.self, name: "host") {
            // <-- how to get the host string like this: let host = AppConfig().host
        }
   }
}

//I want to use the "host" string on a type like this:

struct SomeModel {
   //I want to read the "host" string directly instead of referencing AppConfig type.
    @Injected(name: "host") var host: String
}

//AppConfig is also needed in other models
hmlongco commented 3 years ago

You can resolve anything inside of a registration. That's how constructor injection works. In your example, however....

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        register { AppConfig()  }
        register(name: "host") { resolve(AppConfig.self).host  }
   }
}

No need for the String.self, as Resolver will infer the return type from the factory result.

I would probably go the extra mile here with name spaces...

extension Resolver.Name {
    static let host = Self("host")
}

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        register { AppConfig()  }
        register(name: .host) { resolve(AppConfig.self).host  }
   }
}

struct SomeModel {
    @Injected(name: .host) var host: String
}

Name spaces help ensure consistency and minimizes the chance for errors, like attempting to do...

struct SomeModel {
    @Injected(name: "Host") var host: String
}
murad1981 commented 3 years ago

@hmlongco Thank you for the explanation, I liked the idea of name spaces too ... However, if the above setup uses an enum for the host instead of String the app will crash with the error: Fatal error: RESOLVER: 'String:host' not resolved. To disambiguate optionals use resolver.optional()

enum BaseUrl: String {
    case staging = "staging.com"
    case production = "prod.com"
}

struct AppConfig {
    var host: BaseUrl = .staging
}

extension Resolver.Name {
    static let host = Self("host")
}

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {

        register {
            AppConfig()
        }

        register(name: .host) {
            resolve(AppConfig.self).host
        }
    }
}

I struggled to solve the error but to no avail unfortunately .. the following is a ContentView in case you want to test it:

struct ContentView: View {
    @Injected(name: .host) var host: String

    var body: some View {
        Text("Hello, world!")
            .padding()
            .onTapGesture {
                print("host = \(host)")
            }
    }
}
hmlongco commented 3 years ago

Ummm.... isn't the type now BaseUrl and not String?

@Injected(name: .host) var host: BaseUrl

That should work, though I've never tried injecting an enumeration before.

murad1981 commented 3 years ago

yeah totally ... sorry .. it was a typo though ...