15.8 · Closure-based clients, dependency lifetimes, generic repositories and building the graph lazily

The first pass said to inject through initialisers. The questions that follow in a real codebase are about the shape of what you inject, how long it lives, how generic it is and when it gets built.

Mental model. Every dependency has a shape (protocol, struct of closures, generic), a lifetime (app, session, screen) and a moment of creation. Choose each on purpose.

Shape of a dependency

Protocol or struct of closures, and protocols with associated types.

  1. When is a struct of closures a better dependency than a protocol, and what do you give up? 15.8.1 · L5
    A protocol with five methods needs a new conforming type for every test double, and each double must implement all five, including the four the test never…
  2. How do you inject a protocol that has an associated type: primary associated types, any versus some, and generic view models? 15.8.2 · L5
    A Repository with associatedtype Model cannot be used as a plain type: let repo: Repository is not allowed, because the compiler does not know what all()…

Lifetime and creation

Scopes that die at logout, and a graph that is not built at launch.

  1. How do you scope dependencies by lifetime (app, session, screen), and what leaks when a longer-lived object captures a shorter-lived one? 15.8.3 · L5
    Not every dependency lives as long as the app. App scope: logging, analytics, the HTTP client, feature flags. Session scope: everything tied to a signed-in…
  2. Why should the object graph not be built eagerly at launch, and how do lazy factories keep the composition root cheap? 15.8.4 · L5
    A composition root that constructs every feature’s dependencies in its initialiser runs all that work before the first frame: databases opened, SDKs…

More in Architecture & API design