AliSoftware / Dip

Simple Swift Dependency container. Use protocols to resolve your dependencies and avoid singletons / sharedInstances!
MIT License
977 stars 72 forks source link

Objects not identical when using container collaboration #102

Closed basalphenaar closed 8 years ago

basalphenaar commented 8 years ago

Hello,

First of all, thanks to all for making this well-documented and easy to use (most of the time) framework :) I'm having a hard time understanding why someClass1 and someClass2 in my Playground code below are not pointing to the same object as someClassFromSecondContainer. Because SomeClass/SomeProtocol is registered as a singleton and rootContainer and secondContainer are collaborating I would expect that a resolve via secondContainer would point to the same object as a resolve via rootContainer.

I'm resolving with rootContainer twice in my code below (someClass1 and someClass2) to prove that multiple resolves of a singleton from the the same container do indeed point to the same object, however, a resolve from a collaborating container does not.

Is this a bug in Dip or is this expected behaviour? Thanks so much for your help :)

import Dip

protocol SomeProtocol { }
class SomeClass: SomeProtocol { }

let rootContainer = DependencyContainer() { c in
    c.register(.Singleton) { SomeClass() as SomeProtocol }
}

let secondContainer = DependencyContainer() { c in
    // ... Normally I would register some other things here
}

rootContainer.collaborate(with: secondContainer)
secondContainer.collaborate(with: rootContainer)

let someClass1 = try? rootContainer.resolve() as SomeProtocol
let someClass2 = try? rootContainer.resolve() as SomeProtocol
let someClassFromSecondContainer = try? secondContainer.resolve() as SomeProtocol

if let someClass1 = someClass1 as? SomeClass,
let someClass2 = someClass2 as? SomeClass,
let someClassFromSecondContainer = someClassFromSecondContainer as? SomeClass {

    unsafeAddressOf(someClass1) // "UnsafePointer(0x7FCDB05131B0)"
    unsafeAddressOf(someClass2) // "UnsafePointer(0x7FCDB05131B0)"
    unsafeAddressOf(someClassFromSecondContainer) // "UnsafePointer(0x7FA9B1531710)"

    someClass1 === someClass2 // true
    someClass1 === someClassFromSecondContainer // false
    someClass2 === someClassFromSecondContainer // false
}
ilyapuchka commented 8 years ago

Hi @basalphenaar. Can you check the same in the app instead of playground? Sometimes I got weird behaviour in playgrounds. I will take a look at this issue later. Thank you!

ilyapuchka commented 8 years ago

Indeed there is an issue in the framework, thanks for pointing it out! Fix will follow soon.

basalphenaar commented 8 years ago

@ilyapuchka Thanks for the insanely fast fix, will try it out immediately tomorrow!

sdelaysam commented 7 years ago

@ilyapuchka seems it's not working 100% right I'm running tests in release 4.6.1

When resolving after collaboration, all is ok

    let container1 = DependencyContainer()
    container1.register(scope) { ServiceImp1() as Service }

    let container2 = DependencyContainer()
    container2.collaborate(with: container1)

    let service1: ServiceImp1 = try! container1.resolve()
    let service2: ServiceImp1 = try! container2.resolve()

    XCTAssertTrue(service1 === service2) // ok

In this case services are not equal

    let container1 = DependencyContainer()
    container1.register(scope) { ServiceImp1() as Service }
    let service1: ServiceImp1 = try! container1.resolve()

    let container2 = DependencyContainer()
    container2.collaborate(with: container1)
    let service2: ServiceImp1 = try! container2.resolve()

    XCTAssertTrue(service1 === service2) // failed
ilyapuchka commented 7 years ago

@sergthedeveloper thanks for mentioning that. If you look at the implementation of collaborate(with:) method you will see that it overrides resolved instances pool of container in parameter with resolved instances pool of container on which this method is called so that they share those pools. So here it will override instances pool of container1 which contains resolved instance with instances pool of container2 which is empty. And on next resolve it creates new instance. That might be considered as a bug, but I would strongly discourage from using containers like that and only resolve after they are fully set up.

sdelaysam commented 7 years ago

@ilyapuchka Ok, I see. Here is my scenario:

I have one root container with application-wide services (like ServerApi or Preferences or whatever) and some scoped containers for separate screens or user stories with story-oriented services (like PurchaseModel).

Scoped container depends on root and meant to be initialized later, when corresponding screen or user story begins and destroyed when story ends (container's reset() method should do the trick, right?).

So my concern was to make subcontainers depending on (collaboration with) root container. All of these subcontainers should be using the same instance of ServerApi or Preferences from root, whenever they are instantiated.

What I'm doing now is registering already instantiated objects, not factories, in root container. But it kills benefit of lazy instantiation.

Any advice?

ilyapuchka commented 7 years ago

@sergthedeveloper there is no reason to create containers later at runtime. They are lightweight and basically store just two maps between definition key and definition and instances pool for singleton scoped definitions only. So you can simply set them up right away. There is no need to call reset on container too as it will remove all registered definitions, not just resolved instances. Also it will make you reference container in your code, which is also strongly discouraged. Basically reset might be helpful for tests.

If I understood correctly you want to re-instantiate some graph of objects specific for some user story when user goes into it. For that you should have some root object in that graph scoped as WeakSingleton. So when user leaves user story you simply nilify all references to this object that you have in your code and then the whole graph will be deallocated (as container holds a weak reference to such instances). The rest of the setup does not change. You still register root components in a root container and user story specific components in separate containers. And make them to collaborate with root container.

You can look into VIPER-SWIFT example and play with it, there are a lot of comments to help and about using weak singletons too. I hope it will help.

sdelaysam commented 7 years ago

@ilyapuchka thanks, will have a look!