Effortless dependency injection for Swift
Inject
The Inject framework provides a declarative Swift API for injecting instances of dependencies into components that depend on them.
- You declare instance that is going to be provided by default in production by extending
DefaultValues
. - Declare the injection point where the default/production is used using the
@Injected
property wrapper e.g. in your SwiftUIView
. - Later you can inject other instances when you need them, e.g. tests or previews by calling
.injecting(newInstance, for: \.service)
.
The API and implementation are very similar to Environment
and EnvironmentValues
from SwiftUI
but unlike Environment
it works across all your code and not only in View
s.
Framework performance overhead is absolutely minimal. When you inject dependency only a value of a variable that holds this new instance is changed. Default values are computed properties so they get created only when istance is actually used.
By adopting Inject, you’ll make your code easier to read and maintain, by centralizing your dependency injection code and eliminating troublesome techniques like shared instances, complex init
methods with many parameters, passing dependencies through the classes that don’t need them, etc. Code that ensures injecting is completely gone and replaced with declarative one-liners to define the injection points.
Getting started
To enable effortless injection:
- Define the default/production value anywhere in your code.
extension DefaultValues {
var imageDownloader: ImageDownloaderInterface { ImageDownloader() }
}
- In the class you need it, mark the injection point with
@Injected(\.DefaultValues.keyPath)
@Injected(\.imageDownloader) var downloader // dependency of the view
-
Mark your class with the
Injectable
protocol to enableinjecting
function. -
In tests or previews or anywhere you need replace the default instance with the one you want to see there.
RemoteImage() // will use production value
RemoteImage() // will use MockDownloader
.injecting(MockDownloader(), for: \.downloader)
RemoteImage() // will use MockWithDelay
.injecting(MockWithDelay(ms: 200), for: \.downloader)
Installation
Adding the dependency
Inject is designed for Swift 5. To depend on the Inject package, you need to declare your dependency in your Package.swift:
.package(url: "https://github.com/MaximBazarov/Inject.git", branch: "main"),
and to your application/library target, add “Inject” to your dependencies, e.g. like this:
// Target syntax for Swift up to version 5.1
.target(
name: "BestExampleApp",
dependencies: ["Inject"]
),
// Target for Swift 5.2
.target(
name: "BestExampleApp",
dependencies: [
.product(
name: "Inject", // you can set any name you prefer here
package: "Inject"
)
],