Liftric / DIKit

Dependency Injection Framework for Swift, inspired by KOIN.
MIT License
102 stars 17 forks source link

Thoughts: loss of types #6

Closed guidoschmidt closed 6 years ago

guidoschmidt commented 6 years ago

When playing around with the example application I kind of stumbled upon a potential issue with the implementation itself:

Having a DependencyContainer register a service:

public extension DependencyContainer {
    static var app: DependencyContainer {
        return DependencyContainer { container in
            container.register(as: .prototype) { 
                 // This will register LocalStorage explicitly with it's protocol
                 LocalStorage() as LocalStorageProtocol
            }
        }
    }
}

The type of the registered service/component kind of gets lost when using the inject() method: As a developer I may not know what type of services are registered :thinking:

class ModalViewController: UIViewController {
    let storage: LocalStorageProtocol = inject() // This will compile and will run
    let storage: LocalStorage = inject() // This will compile but won't run

    @IBAction func close(_ sender: Any) {
        dismiss(animated: true)
    }
}

I'm not sure if that's possible from my current understanding of the DIKit code but having something like inject(ofType: LocalStorage) could help? Maybe the resolve() function could be improved to use stacks of components/instances that are better typed that using a String representation? 🤔

benjohnde commented 6 years ago

inject(ofType: T) does not make a lot of sense because the method currently is generic and the type gets inferred by the type of the variable. You would also need to use type(of: LocalStorage), thus resulting in the ugly form of:

class ModalViewController: UIViewController {
    // This will compile and will run
    let storage: LocalStorageProtocol = inject()

    // This will compile and will run
    let storage: LocalStorage = inject(type(of: LocalStorageProtocol))

    // This will compile but won't run
    let storage: LocalStorage = inject(type(of: LocalStorage))

    @IBAction func close(_ sender: Any) {
        dismiss(animated: true)
    }
}

In the third example, it also would compile but won't run. To avoid those cases we would need some additional dependency graph generation and throw compile errors of the graph is not fulfilled.

benjohnde commented 6 years ago

What I thought about was forcing the developer to register a protocol (T == Protocol) in order to avoid registrations of pure components. This would then throw compile errors when using the wrong protocol, i.e. `Backend() as LocalStorageProtocol).

But sadly, this is not possible with Swift as of now.

But coming back to: let storage: LocalStorage why is it bad to use the class and not the protocol here. We want to be able to change the actual implementation (e.g. with mock objects) for testing purpose.

Maybe you come up with a different approach?

benjohnde commented 6 years ago

Settled for the first release even if the choice is maybe not the best the best. We can change that later on.