liftric / dikit Goto Github PK
View Code? Open in Web Editor NEWDependency Injection Framework for Swift, inspired by KOIN.
License: MIT License
Dependency Injection Framework for Swift, inspired by KOIN.
License: MIT License
Ability to add components linked with a context / scope in order to instantiate and release objects for a very specific target / action.
For testing it's convenient to be able to destroy the context after each test and set it up before.
A component which is not always resolvable.
I stumbled upon the Scope enum and found the prototype
case a bit misleading:
/// Defines the `Component` scope.
public enum Scope {
/// The same instance of the class is returned each time it is injected.
case singleton
/// A new instance of the class is created each time it is injected.
case prototype // Consider renaming this to `clone` or `instance`?
}
// I'd consider prototype as a term for some form of an abstract class
// the attached documentation above even describes the behavior as creating an instance
container.register(as: .prototype) { ComponentX() }
// I'd found one of these more meaningful
container.register(as: .clone) { ComponentX() }
container.register(as: .instance) { ComponentX() }
Breaks the API but I like the intent of an action (which can also break) a lot more of being an injectable
.
Currently lazy injection works through a different computed propery behaviour. Due to using an enum for @Inject
it is mutating. Hence it is not really useful in a struct, as we are not allowed to alter a variable within a struct Cannot use mutating getter on immutable value: function call returns immutable value
. And as 'mutating' may only be used on 'func' declarations
we need a different approach.
Add the ability to resolve components by name
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 🤔
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? 🤔
The @OptionalInject wrapper only works if one provides a non-optional component. If the component is optional, the @OptionalInject wrapped instance always ends up nil.
This appears to be because the component is registered using its optional type (e.g. Optional<ComponentF>
) but then the resolve fails as it is looking for non-optional type (e.g. ComponentF
).
This can be demonstrated in DIKitTests testOptionalInjection by making ComponentF
's init optional.
Doing this causes the test to fail.
To be quite honest: I don't really care what sort of CI is being used. I have no strong opinion on that.
Removal of DefinesContainer
, use proper syntax and create some kind of app context for the shared instance of the root container.
Hi I'm trying to use this library with the new SwiftUI lifecycle that doesn't have an AppDelegate, it seemed ok to initialize it in the "App" class (the one with @main annotation) but when i do, it throws an error "Fatal error: root
DependencyContainer is not yet set."
@main
class ChopsticksApp: App {
@Inject var viewModel: StartupViewModel
required init() {
FirebaseApp.configure()
DependencyContainer.defined(by: modules { .main; .restaurant; .common; .restaurantList })
}
var body: some Scene {
WindowGroup {
}
}
}
Also this method of initialization is giving another error
The modules are defined like this
public extension DependencyContainer {
static var main = module {
single { FirebaseApp.app() as FirebaseApp? }
}
}
Hello, i have a bug when trying to resolve a dependency that i have in multiple targets
My componentStack is this:
(lldb) po self.componentStack
▿ 8 elements
▿ 0 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Restaurants.RestaurantsLocalDataSource))
▿ value : ComponentIdentifier
- tag : nil
- type : Restaurants.RestaurantsLocalDataSource
▿ value : <Component<RestaurantsLocalDataSource>: 0x600000b0c060>
▿ 1 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Restaurants.RestaurantsRemoteDataSource))
▿ value : ComponentIdentifier
- tag : nil
- type : Restaurants.RestaurantsRemoteDataSource
▿ value : <Component<RestaurantsRemoteDataSource>: 0x600000b0c120>
▿ 2 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: RestaurantList.RestaurantListViewModel))
▿ value : ComponentIdentifier
- tag : nil
- type : RestaurantList.RestaurantListViewModel
▿ value : <Component<RestaurantListViewModel>: 0x600000b14360>
▿ 3 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Chopsticks.StartupViewModel))
▿ value : ComponentIdentifier
- tag : nil
- type : Chopsticks.StartupViewModel
▿ value : <Component<StartupViewModel>: 0x600000b043c0>
▿ 4 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Swift.Optional<__C.FIRApp>))
▿ value : ComponentIdentifier
- tag : nil
- type : Swift.Optional<__C.FIRApp>
▿ value : <Component<Optional<FIRApp>>: 0x600000b04360>
▿ 5 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: RealmSwift.Realm))
▿ value : ComponentIdentifier
- tag : nil
- type : RealmSwift.Realm
▿ value : <Component<Realm>: 0x600000b14240>
▿ 6 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Restaurants.RestaurantRepository))
▿ value : ComponentIdentifier
- tag : nil
- type : Restaurants.RestaurantRepository
▿ value : <Component<RestaurantRepository>: 0x600000b0c000>
▿ 7 : 2 elements
▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: FIRFirestore))
▿ value : ComponentIdentifier
- tag : nil
- type : FIRFirestore
▿ value : <Component<FIRFirestore>: 0x600000b14300>
The identifier is this:
(lldb) po identifier
▿ ComponentIdentifier
- tag : nil
- type : FIRFirestore
But this still finds no matching component in the stack.
I have a module providing the dependency in one target (dynamic framework) of my app, and the dependency itself is requested in another target
Could be implemented by traversing the Sources and collect each @Inject
for creating the dependency-graph. If no circular dependencies are found, continue with collecting information about the dependency container creations and derive the final module catalogue. From here on it is rather easy to validate.
We want to test the full cycle with an running mobile app (which gets tested) within a sim.
It should be so useful, expose another method beyond the one already declared to permit to set at runtime a new dependency container.
You have a strict rule on setting root (and it's clear the motivation) but if I want to inject a new dependecies at runtime for unit test?
Let me know and thanks a lot for this useful lib.
Currently, a propertyWrapper can neither be used on local variables nor on a constructor / functions. There is already a possibility to use constructor dependency injection like:
let backend: BackendProtocol
init(backend: BackendProtocol = resolve()) {
self.backend = backend
}
This could be implemented by checking if all registered components in a container can resolve all their dependencies. Some other ideas?
Add support for Kotlin/Native, still depends on proper graph validation.
I'm trying to cover a use case when the root module needs to be rebuilt due to an API environment change. Once the network client changes, the affected modules should be redefined.
In Koin there's a loadKoinModules
that lets the developer replace affected modules after startKoin
is called (reset cached data when the user logs out).
Let me know if you had something similar in mind and I'm happy to contribute.
This would also fix #42
Quite unsure how that could looks like.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.