niksativa/dikit
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.
Create container
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()
])SwiftUI
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()
}
}
}Create assembly
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)
}
}How to resolve dependencies
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: DataBaseRegistration
Most basic registration looks like this:
registrator.register(BuildMode.self, entity: BuildMode.init)options
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
'Named' option
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'.implements'
Multiple implementations of different protocols in one class
registrator.register(UserDefaults.self) {
return UserDefaults.standard
}
.implements(DefaultsStorage.self)Custom entity key (EntityKeyProviding)
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.
Arguments
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
}Package Metadata
Repository: niksativa/dikit
Default branch: main
README: README.md