Constructor Injection
Pass dependencies through initializers.
Constructor Injection is a free Swift Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Constructor Injection
The most common DI style passes dependencies through an initializer. The object receives everything it needs at creation time and stores them.
A Basic init
Declare a stored property and assign it in init. Callers must supply the dependency to create the object.
struct Greeter { func hi() -> String { "Hi" } }
struct View {
let greeter: Greeter
init(greeter: Greeter) { self.greeter = greeter }
func show() { print(greeter.hi()) }
}
View(greeter: Greeter()).show()Immutable Dependencies
Using let for injected properties makes them immutable, so a dependency cannot be swapped after construction.
struct Engine { let hp: Int }
struct Car {
let engine: Engine // cannot change later
init(engine: Engine) { self.engine = engine }
}
let c = Car(engine: Engine(hp: 150))
print(c.engine.hp)Multiple Dependencies
List several parameters to inject more than one collaborator. The initializer documents the full set of requirements.
struct DB { func get() -> Int { 7 } }
struct Cache { func put(_ v: Int) { print("cached \(v)") } }
struct Service {
let db: DB
let cache: Cache
init(db: DB, cache: Cache) { self.db = db; self.cache = cache }
func run() { cache.put(db.get()) }
}
Service(db: DB(), cache: Cache()).run()Default Parameter Values
A default value lets callers omit a dependency in production while still allowing overrides in tests.
struct Logger { func log(_ m: String) { print(m) } }
struct Service {
let logger: Logger
init(logger: Logger = Logger()) { self.logger = logger }
func run() { logger.log("go") }
}
Service().run() // uses defaultClass Constructor Injection
Classes follow the same pattern. The initializer assigns each dependency before the instance is usable.
class Mailer { func send() { print("sent") } }
class Signup {
let mailer: Mailer
init(mailer: Mailer) { self.mailer = mailer }
func go() { mailer.send() }
}
Signup(mailer: Mailer()).go()Injecting Values
Dependencies need not be objects. You can inject configuration values like a base URL or a flag the same way.
struct Client {
let baseURL: String
init(baseURL: String) { self.baseURL = baseURL }
func endpoint(_ p: String) -> String { baseURL + p }
}
print(Client(baseURL: "https://api").endpoint("/users"))Composition Root
One place near the app's entry point wires the whole object graph. Everything else just receives ready-made dependencies.
struct DB { func count() -> Int { 3 } }
struct Repo { let db: DB; func n() -> Int { db.count() } }
struct App { let repo: Repo; func run() { print(repo.n()) } }
// Composition root:
let app = App(repo: Repo(db: DB()))
app.run()Avoiding Hidden Globals
Constructor injection replaces hidden global access. Each dependency is explicit, so you can trace how data flows.
struct Settings { let theme: String }
struct Screen {
let settings: Settings
func render() { print("Theme: \(settings.theme)") }
}
Screen(settings: Settings(theme: "dark")).render()Required vs Optional
If a dependency is truly optional, model it with an optional type; otherwise require it so an object can never exist half-configured.
struct Analytics { func track() { print("tracked") } }
struct Flow {
let analytics: Analytics?
func step() { analytics?.track() }
}
Flow(analytics: Analytics()).step()
Flow(analytics: nil).step()Why Constructor Injection
It guarantees a fully initialized object, makes dependencies visible, and works with let immutability. It is the recommended default in Swift.
struct Adder { func add(_ a: Int, _ b: Int) -> Int { a + b } }
struct Calc {
let adder: Adder
init(adder: Adder) { self.adder = adder }
}
print(Calc(adder: Adder()).adder.add(4, 5))Quick Check
Why is constructor injection often preferred?
Recap
Constructor injection passes dependencies through init and stores them, usually as immutable let properties. You can inject objects or values, supply defaults, and wire everything in a composition root. Next you will inject protocols rather than concrete types.
Frequently asked questions
Is the “Constructor Injection” lesson free?
Yes — the full text of “Constructor Injection” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “Constructor Injection”?
Pass dependencies through initializers. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Constructor Injection” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Why Dependency Injection
- Constructor Injection
- Protocol-Based Abstractions
- Mocking Dependencies in Tests