AliSoftware / Dip

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

One concrete class for multiple singletons #196

Closed Narayane closed 6 years ago

Narayane commented 6 years ago

Hi,

I have created 3 protocols for evolutivity and testability reasons. For now, I have only one concrete class which implements these protocols.

I declare this in my DI configuration:

self.register(.singleton) { MyDataSource() as AuthProtocol } self.register(.singleton) { MyDataSource() as RealTimeDatabaseProtocol } self.register(.singleton) { MyDataSource() as FileStorageProtocol }

Will MyDataSource be instantiated only one time by Dip? If not, how can I do this?

Thanks.

iAmNaz commented 6 years ago

@Narayane can you just register it with a tag?

When it is needed just cast it to how you will use it?

Named Definitions

Narayane commented 6 years ago

@iAmNaz Not sure to understand your words, can you give an example relative to my case.

I think my aim is a little bit different, if i understand well named definitions examples. I don't want to have different implementations for a given protocol (dependant of an environment or whatever). I want to be sure thanks to Dip than only one instance of a concrete class will be used to resolve 3 different singletons

iAmNaz commented 6 years ago

Assuming this is your concrete class, correct? class MyDataSource: AuthProtocol, RealTimeDatabaseProtocol, FileStorageProtocol {}

Registration: container.register(tag: "DataSourceTag") { MyDataSource() as MyDataSource } OR container.register(.singleton) { MyDataSource() as MyDataSource }

Usage: let dataSource = try container.resolve(tag: "DataSourceTag" ) as MyDataSource OR let dataSource = try container.resolve( ) as MyDataSource

Use your datasource as normal and if you need it to be of some protocol then just cast it let auth = dataSource as! AuthProtocol

ilyapuchka commented 6 years ago

@Narayane try to use type forwarding for this - https://github.com/AliSoftware/Dip/wiki/type-forwarding

ilyapuchka commented 6 years ago

@iAmNaz named definitions should be used to distinguish registrations of the same type which return different implementations depending on label, in this case its required to resolve different types to the same instance, so even if named definitions would work that would be a not a good solution IMO

Usipov commented 6 years ago

We have a solution to perform registartion chaining. Assuming the Class conforms to Protocol1 and Protocol2:

container.register {
    Class()
}.reregister {
    $0 as Protocol1
}.reregister {
    $0 as Protocol2
}

with another scope:

container.register(.singleton) {
    Class()
}.reregister {
    $0 as Protocol1
}.reregister {
    $0 as Protocol2
}

Implementation:

public final class ContainerPromise<T, U> {

    let container: DependencyContainer
    let definition: Definition<T, U>

    init(
        _ definition: Definition<T, U>,
        _ container: DependencyContainer)
    {
        self.definition = definition
        self.container = container
    }

    @discardableResult public func reregister<T2>(
        factory: @escaping (T) -> T2)
        -> ContainerPromise<T, U>
    {
        container.register(
            resolvable: T.self,
            factory: factory
        )
        return self
    }

}

public extension DependencyContainer {
    func promise<T, U>(_ definition: Definition<T, U>) -> ContainerPromise<T, U> {
        return ContainerPromise(definition, self)
    }
}

public extension DependencyContainer {
    @discardableResult public func register<T, R>(
        _ scope: ComponentScope = .shared,
        resolvable: R.Type,
        type: T.Type = T.self,
        tag: DependencyTagConvertible? = nil,
        factory: @escaping (R) -> T)
        -> ContainerPromise<T, ()>
    {
        let definition = register(scope, type: type, tag: tag) { [weak self] () -> T in
            guard let strongSelf = self else {
                fatalError(
                    "Can't register object with type \(T.self) " +
                    "because container deallocated"
                )
            }
            let definition = factory((try strongSelf.resolve() as R))
            return definition
        }
        return promise(definition)
    }
}