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 {
@DIObservedObject 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: DataBaseof SwiftUI:
@DIObservedObject var api: API
@DIStateObject var viewState: ReCreatedState
@DIProvider var dataBase: DataBaseMost 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: ThemeMultiple implementations of different protocols in one class
registrator.register(UserDefaults.self) {
return UserDefaults.standard
}
.implements(DefaultsStorage.self)The container derives the storage key for each registration from the type's runtime metadata (ObjectIdentifier). For most types this is unique and stable.
There is one Swift-level exception: parameterized existentials (any P<X>). For such types String(reflecting:) returns the literal string "<<< invalid type >>>", and _mangledTypeName can return nil when the generic parameter crosses a module boundary. DIKit itself now relies on ObjectIdentifier instead, which works for the common cases — but if you hit a crash like this:
DIKit/Container.swift:?: Fatal error: <<< invalid type >>> is already registered with <<< invalid type >>>
two distinct registrations have collapsed onto the same key. Conform the type to EntityKeyProviding to pin an explicit key:
import DIKit
extension FeatureFlagManaging: EntityKeyProviding {
public static var entityKey: String { "FeatureFlagManaging<FeatureFlag>" }
}The same key is used for both registration and resolution, so every call site for that type sees the same storage bucket. Two unrelated types may deliberately share the same entityKey to alias one onto the other.
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
}