Tutorials › Launch Lab Notes › Chapter 21

Swap the Service

LabChapter 21 of the Launch Lab Notes22 minAugust 20, 2026Intermediate

How do you photograph three backends without rebuilding the app?

Wire the screen to a protocol. Pass live, stub, or fail at launch. Keep a hardwired Live card as the control that ignores the argument.

Filmstrip: HARDWIRED Live stays READY while INJECTED cycles LIVE, STUB, then NETWORK DOWN on FAIL.

Protocol first

protocol BuildStatusClient: Sendable {
  var sourceLabel: String { get }
  func fetchLatest() async -> BuildPulseOutcome
}

Three conformers. LiveBuildClient sleeps ~280 ms then returns Harbor. StubBuildClient returns the same READY instantly. FailingBuildClient returns NETWORK DOWN.

The injected card takes any BuildStatusClient from outside. That is initializer injection — the dependency arrives when the view is built, not from a singleton inside the body.

Live — both look the same

-mode live pins the client selector to LIVE.

CLIENT LIVE. HARDWIRED SOURCE LIVE READY Harbor v1.4.0 build 418. INJECTED SOURCE LIVE READY Harbor v1.4.0 build 418. NOW LIVE pinned.
PanelSourceResult
HARDWIREDLIVEREADY · Harbor v1.4.0 · build 418
INJECTEDLIVEREADY · Harbor v1.4.0 · build 418

Same numbers. Injection is invisible until you swap the client.

Stub — source changes, payload matches

-mode stub.

CLIENT STUB. HARDWIRED SOURCE LIVE READY build 418. INJECTED SOURCE STUB READY Harbor v1.4.0 build 418. NOW STUB pinned.
PanelSourceResult
HARDWIREDLIVEREADY · build 418
INJECTEDSTUBREADY · build 418

Hardwired still says SOURCE LIVE and ignores -mode. Injected flips to SOURCE STUB with the same Harbor pulse — a deterministic stand-in for tests and screenshots.

Fail — the frame that proves DI

-mode fail.

CLIENT FAIL. HARDWIRED SOURCE LIVE READY Harbor v1.4.0 build 418. INJECTED SOURCE FAIL ERROR NETWORK DOWN no pulse. NOW FAIL pinned.
PanelSourceResult
HARDWIREDLIVEREADY · build 418
INJECTEDFAILERROR · NETWORK DOWN · no pulse

One screen. One launch argument. The control stays healthy while the injected path shows the outage. That is substitutability photographed — not argued.

Same flag as the capture plan

scripts/lab/shots/plans/injection.json launches -mode live|stub|fail. The bench and its screenshots share one resolution path. No second UI for “demo mode.”

Mini-exercise

Add a fourth client, SlowStubBuildClient, that sleeps two seconds then returns build 999. Pin -mode slow and confirm HARDWIRED still shows 418 while INJECTED shows 999.

Challenges

  1. Move client resolution into a tiny BuildStatusFactory with one make(mode:) method — Design Patterns’ factory, kept local.
  2. Unit-test FailingBuildClient without launching the simulator.
  3. Property-inject a second optional logger after init; leave the required client on the initializer.
  4. Replace the hardwired card with a Service Locator singleton and photograph why the control got harder to pin.
  5. Wire XCUITest to assert NETWORK DOWN appears under -mode fail and never under -mode live.

Key Points

Next up: architecture patterns — the same feature as MV, MVVM, and a reducer, with body-run counts on each so the comparison is measured.

SwiftUI
SwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.
Swift
Swift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.
Ship iOS
Shipping workflows for iOS apps.
📚 Go deeper with LIPAI WANG’s hands-on Udemy bootcampsBrowse all courses →
← Ch 20: What the Tests CatchCh 22: Same Feature, Three Bodies→
SwiftUIUltimate SwiftUI SeriesSwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.SwiftUltimate Swift SeriesSwift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.Ship iOSShip iOS Apps SeriesShipping workflows for iOS apps: signing, TestFlight, App Store Connect, CI, and release hygiene.

Ship your apps faster

When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.

Try Simple App Shipper
5 free articles remainingSubscribe for unlimited access