Skip to content

Property Wrapper

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

Given an assembly with bindings:

override func makeBindings() {
    super.makeBindings()
    makeInjectable(knight, byType: Knight.self)
}

We can inject built instances into top-level classes using the @Assembled property wrapper, as follows:

class StoryViewController : UIViewController {

    /**
     The built instance is auto-injected from the QuestAssembly 
    */
    @Assembled var knight: Knight; 

    private(set) var story: Story 
  
    init(story: Story) {
      self.story = story
    }
}

Pertinent points to note are:

  • Pilgrim is a DI container and the @Assembled property wrapper is a convenient way to obtain built instances, and inject them as dependencies into top-level components.
  • Use the assembly to define how components interact. For example a Quest is injected into a Knight - this happens within the assembly.
  • Use the Assembled property decorator to obtain built instances for injection into top-level components - eg a view controller, in a UIKit app.

Default Assembly

The default assembly is specified when an application is bootstrapped, for example in UIApplicationMain:

class PocketForecastApplication: UIApplication {

    override init() {
        AssemblyHolder.defaultAssemblyType = ApplicationAssembly.self
    }

}

When the default assembly is not set, or we wish to explicitly override, it can be specified as follows:

@Assembled(assembly: QuestAssembly.self) var knight: Knight

Injecting by Key

In general we bind by type, for example a protocol to a concrete implementation:

override func makeBindings() {
    super.makeBindings()
    makeInjectable(weatherClient, byType: WeatherClient.self)
}

func weatherClient() -> WeatherClient {
    objectGraph {
        WeatherClientWorldWeatherImpl(reportRepo: weatherReportRepository(), serviceUrl: config.serviceUrl,
                daysToRetrieve:config.daysToRetrieve, apiKey: config.apiKey)
    }
}

When necessary, for example if we have two implementations representing the same protocol, we can bind to a string key:

makeInjectable(holyGrailQuest, byKey: "holyGrailQuest")

Injecting the Assembly

A binding for the assembly is made by default, so give the following:

class QuestAssembly: PilgrimAssembly {

override func makeBindings() {
    super.makeBindings()
    makeInjectable(knight, byType: Knight.self)
    makeInjectable(holyGrailQuest, byType: Quest.self)
}

    func knight() -> Knight {
        objectGraph(Knight(quest: holyGrailQuest()))
    }

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

We can obtain the assembly using:

@Assembled var assembly: PilgrimAssembly

This is useful when using the assembly as a factory with runtime-arguments.

We can manually bind an assembly with a protocol. Say we create a ViewCoordinator protocol representing a factory that can emit view controllers:

public class ApplicationAssembly: PilgrimAssembly, ViewCoordinator {

    public override func makeBindings() {
        makeInstanceInjectable(self, byType: ViewCoordinator.self)
    }

}

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