Inject

Usage | Installation

The Inject framework provides a declarative Swift API for injecting instances of dependencies into components that depend on them.

  1. You declare instance that is going to be provided by default in production by extending DefaultValues.
  2. Declare the injection point where the default/production is used using the @Injected property wrapper e.g. in your SwiftUI View.
  3. 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 Views.

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:

  1. Define the default/production value anywhere in your code.

extension DefaultValues {
    var imageDownloader: ImageDownloaderInterface { ImageDownloader() }
}
  1. In the class you need it, mark the injection point with @Injected(\.DefaultValues.keyPath)
@Injected(\.imageDownloader) var downloader   // dependency of the view
  1. Mark your class with the Injectable protocol to enable injecting function.

  2. 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"
        )
    ],

GitHub

View Github