hmlongco / Resolver

Swift Ultralight Dependency Injection / Service Locator framework
MIT License
2.15k stars 190 forks source link

Customization of a Service which is registered via .implements() #55

Closed PhilippSchloesser closed 3 years ago

PhilippSchloesser commented 4 years ago

Hey there, thanks for making Resolver available to all of us!

I just ran into a problem when trying something like this

main.register { MyService() }.implements(MyProtocol.self).scope(customScope)

It took me quite a while to figure out that the registration for MyProtocol is created using the default scope rather than my customScope. This is because .implements() returns the original registration rather than the one it just created. This probably makes sense if you chain it with other things but it is not really obvious from the outside when using the .implements() call. Also what would be the best way to customize my .implements() registrations?

hmlongco commented 4 years ago

Guess I'm not seeing the problem. The scope only comes into play when a service is resolved. Attempting to resolve MyProtocol.self will cause Resolver to recurse and attempt to resolve MyService.self, which may or may not be in customScope. If it is, the original instance is returned. If not, a new instance of MyService is created and placed into customScope and then returned as MyProtocol.

It's true that MyProtocol.self isn't the type in the scope, but MyService is and it's the actual implementation. If you reset customScope, then the next request for MyProtocol will result in a new MyService. (Assuming, I guess, that you didn't set the global scope to application or some such.)

(Deleted this to double-check something and then reposted.)

PhilippSchloesser commented 4 years ago

Hej @hmlongco , thank you for the quick reply! From what I was seeing, I was under the impression that when I am resolving MyProtocol, Resolver will cache the instance of MyService in the scope of the protocol and thus not reset it when I reset customScope. So, for a bit more context: I stumbled upon this when writing unit tests. We in fact do set the default scope to application as pretty much all of our services are singletons. But in the tests, we are not using the application scope (or so I thought). The setup looks like this:

  override func setUp() {
    super.setUp()
    myMockedService = MyMockedService()
    mockContainer = Resolver()
    mockScope = ResolverScopeApplication()

    mockContainer.register { self.myMockedService}.implements(MyProtocol.self).scope(mockScope)
}

  override func tearDown() {
    mockContainer = nil
    mockScope = nil
    super.tearDown()
  }

Now, since I am registering the reference to self.myMockedService in my mockContainer using the mockScope, I was expecting that resolving MyProtocol in my individual tests would always give me the MyMockedService that I freshly created for this very test. However, I'm always getting a reference to the same old instance. Now, I assume this happens because when I'm resolving MyProtocol, Resolver will check the cache of the application scope, find the "old" MyMockedService-instance and thus won't look further in my newly created scope/container, right?

Again, thanks a lot for your help and effort here!

doozMen commented 4 years ago

I have the same question as this is in my opinion one of the drawbacks when writing unit tests. I was however happy to see that this Injected implementation tries to my that clearer that the bulky Swinject. Anyway I think you need to used ResolverScopeUnique instead of ResolverScopeApplication but that part I'm still trying to get my head around too.

doozMen commented 4 years ago

https://github.com/hmlongco/Resolver/blob/master/Documentation/Scopes.md Does that help?

hmlongco commented 3 years ago

I'm trying to clean these things out. Just be aware that I'm working on unit test examples with Resolver. Thanks.