Swift library that allows you to use a dependency injection pattern in your project by creating a container that holds all the dependencies in one place.
Most recommended way to create a container is to use assemblies. Assemblies are classes that conform to Assembly
protocol and are responsible for registering dependencies in the container.
Container(assemblies: [
FoundationAssembly(),
APIAssembli(),
DataBaseAssembly(),
ThemeAssembly()
])
If you want to use DIKit in SwiftUI you can create a container in the App
struct like this:
@main
struct MyApp: App {
let container = Container(assemblies: [...])
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(container.toObservable())
}
}
}
// somewhere in the code
struct ContentView: View {
@EnvironmentLazy var api: API
var body: some View {
Button("Make request") {
api.request()
}
}
}
If you want to use the container in property wrappers (InjectLazy, InjectProvider..) you can create a shared container like this:
let container = Container(assemblies: [...])
container.makeShared() // <-- make container shared
// somewhere in the code
final class SomeManager {
@InjectLazy var api: API
func makeRequest() {
api.request()
}
}
Most basic assembly should look like this:
final class ApplicationAssembly: Assembly {
// list of assemblies that this assembly depends on
var dependencies: [Assembly] {
return [
AnalyticsAssembly(),
RouterAssembly(),
StoragesAssembly(),
ThemeAssembly(),
UIComponentsAssembly()
]
}
func assemble(with registrator: Registrator) {
registrator.register(UserDefaults.self) {
return UserDefaults.standard
}
registrator.register(NotificationCenter.self) {
return NotificationCenter.default
}
registrator.register(UIApplication.self, options: .transient) {
return UIApplication.shared
}
registrator.register(BuildMode.self, entity: BuildMode.init)
registrator.register(UserManager.self, options: .container, entity: UserManager.init)
}
}
At any place where you have access to container you can resolve dependencies like this:
let api: API = container.resolve()
let dataBase: DataBase = container.resolve()
or if container is shared you can use:
@InjectLazy var api: API
@InjectProvider var dataBase: DataBase
of SwiftUI:
@EnvironmentLazy var api: API
@EnvironmentProvider var dataBase: DataBase
Most basic registration looks like this:
registrator.register(BuildMode.self, entity: BuildMode.init)
container
- creates a single instance and stores it in the container (like a singleton)weak
- resolve weak reference and if it was deallocated it will be resolved againtransient
- resolve new instance every time, never store it in the container
You can register multiple instances of the same type with different names and resolve them by name.
registrator.register(Theme.self, name: "light") {
return LightTheme()
}
registrator.register(Theme.self, name: "dark") {
return DarkTheme()
}
and resolve it like this:
let lightTheme: Theme = container.resolve(name: "light")
let darkTheme: Theme = container.resolve(name: "dark")
@InjectLazy(named: "light") var lightTheme: Theme
@InjectLazy(named: "dark") var darkTheme: Theme
Multiple implementations of different protocols in one class
registrator.register(UserDefaults.self) {
return UserDefaults.standard
}
.implements(DefaultsStorage.self)
You can pass arguments to the registration block
when you don’t know the index of the argument, but you are sure that there is only one type in the arguments:
registrator.register(BlaBla.self, options: .transient) { _, args in
return BlaBla(name: args.first()) <-- by type
}
when you know index of argument:
registrator.register(BlaBla.self, options: .transient) { _, args in
return BlaBla(name: args[1]) <-- by index
}