Skip to content

Types of Injections

Jasper Blues edited this page Jan 6, 2021 · 7 revisions

In this section. . .


All injections are defined within one or more PilgrimAssembly sub-classes.

class QuestAssembly: PilgrimAssembly {

  let childAssembly = ChildAssembly()

  override func makeBindings() {
    super.makeBindings()
    makeInjectable(knight, byType: Knight.self)
    makeInjectable(holyGrailQuest, byType: Quest.self)
    makeInjectable(holyGrailQuest, byKey: "damselQuest")
    importBindings(childAssembly)
  }

  func knight() -> Knight {
    weakShared { 
      Knight(quest: damselInDistressQuest()) 
    }
  }

  /**
   HolyGrailQuest is a struct that conforms to the Quest protocol.
   */
  func holyGrailQuest() -> Quest {
    objectGraph { 
      HolyGrailQuest()
    }
  }

  /**
   Damsel's name is Bruce and the Knight is a lovely lass named Fiona. 
   */
  func damselInDistressQuest() -> Quest {
    objectGraph(DamselInDistressQuest())
  }
}

Initializer Injection

Also known as constructor injection. Here's how to do it:

  func knight() -> Knight {
    weakShared { 
      Knight(quest: damselInDistressQuest()) 
    }
  }

  /**
   HolyGrailQuest is a struct that conforms to the Quest protocol.
   */
  func holyGrailQuest() -> Quest {
    objectGraph { 
      HolyGrailQuest()
    }
  }

Create a function that returns emits a scoped (shared, weakShared, objectGraph or unShared) instance. Initializer injections can be specified as arguments pointing to other items within the assembly, or in a child assembly.


Property Injection

func addCityViewController() -> AddCityViewController {
        objectGraph(AddCityViewController(nibName: "AddCity", bundle: Bundle.main)) { [self]
            (controller: AddCityViewController) in

            controller.cityDao = coreComponents.cityRepository()
            controller.weatherClient = coreComponents.weatherClient()
            controller.theme = themeAssembly.currentTheme()
            controller.rootViewController = rootViewController()
        }
    }

Property injection is done in the configuration closure. In general favor initializer injection over property injection, since initializer injection allows emitting an immutable fully-configured and fail-fast class or struct.

More on the Configuration Closure

Besides performing injections in the configuration closure, a function can be invoked, for example to validate that the instance is in a required state.


Injection with Run-time Arguments

Run-time arguments allow defining a factory on the assembly. Here is an example:

func userDetailsController(user: User) -> UserDetailsController 
{
  objectGraph {
    UserDetailsController(user: user, photoService: self.photoService())
  }
}

The factory pattern is a practice to create objects that mix static dependencies and parametrization. Factories also allow "hiding" dependencies of created objects, which the creator does not have to care about. Combined with Injecting the Assembly itself we can use Pilgrim as a factory for emitting built instances with a mixture of dependencies defined within the composition root and runtime parameters.

For example, we can obtain a UserDetailsViewController with both the static and runtime dependencies as follows:

@Assembled var assembly : ApplicationAssembly

func userDidLogin() {
    let detailsController = assembly.userDetailsController(user: currentUser) 
}

Injecting the Assembly

A pilgrim assembly can be injected itself. This is useful with runtime arguments, where the assembly acts as a factory.

func rootViewController() -> RootViewController {
    shared(RootViewController(mainContentViewController: weatherReportController(), assembly: self))
}

In the example above, the root view controller can use the assembly to build a child controller with a mixture of runtime and static dependencies.

⚠️ Another way to mix runtime and static dependencies is to use the @Assembled property wrapper.

Optionally, the assembly can be backed by a protocol that represents the factory methods.


Circular Dependencies

Sometimes you wish to define objects that depend on each other. For example a ViewController that is injected with a view, and a View that is injected with the ViewController as a delegate.

Pilgrim supports circular dependencies via property injection.

func weatherReportController() -> WeatherReportViewController {
    objectGraph(WeatherReportViewController(view: weatherReportView(),
            weatherClient: coreComponents.weatherClient(), weatherReportRepo: coreComponents.weatherReportRepository(),
            cityRepo: coreComponents.cityRepository(), assembly: self))
}

func weatherReportView() -> WeatherReportView {
    objectGraph(WeatherReportView(frame: CGRect.zero)) { [self] (view: WeatherReportView) in
        view.theme = themeAssembly.currentTheme()
        view.delegate = weatherReportController() // Circular dependency
    }
}

Quick Start!

Get started in two minutes.

Main Track

Get familiar with Pilgrim.

Advanced Topics

Become a Pilgrim expert.

Under the Hood

For contributors or curious folks.

Clone this wiki locally