Swinject: Resolve optionals and implicitly unwrapped optionals

Created on 29 Jul 2017  路  8Comments  路  Source: Swinject/Swinject

I would like to add new feature to Swinject. The following code now fails to resolve:

container.register(Service.self) { _ in Service() }

let vc1 = container.resolve(Service?.self) // nil
let vc2 = container.resolve(Service!.self) //nil

Even though this case rarely bothers regular Swinject users it does matter when you use SwinjectAutoregistration. Any service with optional or implictly unwrapped dependency have to be registered by hand and it brings inconsistency to the api.

I do think the Service, Service? and Service! are logically the same and should resolve in the same service.

I can't implement this in the SwinjectAutoregistration due to generics limitation but it can be done in the Swinject itself. I took inspiration in DIP where the type-forwarding is used for the same purpose. For our case something like this could be implemented in ServiceEntry:

let resolvableTypes: [Any.Type] = [((Resolver)->Service?).self, ((Resolver)->Service!).self]

func canResolve(type: Any.Type) -> Bool {
        return resolvableTypes.contains(where: { $0 == type})
}

and the entries could be found after the current lookup fails with something like this:

let entry = services.filter ({ $1.canResolve(type: key.factoryType) }).first?.value

I would like to get a feedback from @yoichitgy or @jakubvano whether this is something that could be added or whether it might break something.

enhancement

Most helpful comment

馃憤馃徎+1

All 8 comments

What is the motivation to add the feature supporting optionals? What usecases do you expect? Technically we can add the feature, but it introduces complexity to our codebase. IMO it's good to add the feature only if the cost can be paid enough馃捀 (Of course, the feature and implementation are technically interesting馃槈

Hi,
yes, as I mentioned the main motivation would be to support SwinjectAutoregistration.
The typical use case for optional resolution would be something like this:

I have a APIService class that handles network requests and takes optional logger:

class APIService {
   init(logger: Logger?)
}

The idea would be to autoregister the api service like this

container.autoregister(APIService.self, initializer: APIService.init)
//or
container.register(APIService.self) { r in
   APIService(logger: r~>)
}

and the logger would be only used if it's registered in the container.

Use case for implicitly unwrapped optionals is more rare, e.g. using 3rd party objective-c api, but I think we should also support it while we are at it.

I understand this would bring complexity to Swinject internal implementation but it would better the public api for the autoregistration users.

I think type-forwarding is worth implementing because we had requests/questions from time to time (#28, #227, #247), but the optional resolution is not highly prioritized. The policy of Swinject project is making a reliable DI container in production.

In case of the APIService example, injecting an empty implementation to non-optional argument makes APIService simpler because you don't have to unwrap logger in APIService. IMO, we should recommend the empty implementation.

class APIService {
   init(logger: Logger)
}

class EmptyLogger: Logger {
    func log(_ message: String) {
        // Do nothing as empty implementation.
    }
}

Just FYI,,, It can be said Service, Service? and Service! are logically the same, but it can be also said they are different. It just depends on a point of view. If those types are the same, Service and [Service], which is a monad like Service?, are the same.

Ok, you can close this issue.
If the type forwarding is implemented though, the optional and implicitly unwrapped optional resolution would be trivial to implement.

Let's keep this issue open to see whether other people want this feature馃槈

馃憤馃徎+1

Now that #322 and #319 are implemented, one way of doing this would be to implement TypeForwading behavior (as suggested in #322). However after doing some performance checks, this approach could cause a significant slow down of the Container setup, especially for large projects with many registrations.

More feasible route would probably be to use InstanceWrapper concept introduced in #331 once it is finalised.

Implemented by #334

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alynmuntean picture alynmuntean  路  6Comments

petalvlad picture petalvlad  路  8Comments

oronbz picture oronbz  路  3Comments

yoichitgy picture yoichitgy  路  5Comments

irace picture irace  路  5Comments