Dependency Inversion with Protocol Abstractions
Depending on abstractions not concretions using Swift protocols as boundaries.
Dependency Inversion with Protocol Abstractions is a free Swift Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, and your progress syncs across the web and the CoddyKit app. The Swift Academy course includes 4 lessons in total.
Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions (protocols).
// WITHOUT DIP:
class OrderService { let db = SQLiteDatabase() } // depends on concrete
// WITH DIP:
class OrderService { let db: Database } // depends on abstractionDefining the Abstraction
Extract a protocol that captures the contract your high-level module needs.
protocol Database {
func save<T: Encodable>(_ value: T, key: String) throws
func load<T: Decodable>(key: String) throws -> T
}Concrete Implementations
Provide multiple concrete implementations behind the protocol: production, test, and future alternatives.
class SQLiteDatabase: Database { ... }
class InMemoryDatabase: Database { ... }
class KeychainDatabase: Database { ... }Constructor Injection
Pass the abstraction through the initializer — the most explicit and testable injection style.
class ProfileService {
private let db: Database
init(db: Database) { self.db = db }
}Why Not Singletons for Everything?
Global singletons hide dependencies, making code hard to test and reason about.
// Hard to test:
class Service { func run() { MySingleton.shared.doWork() } }
// Easy to test:
class Service { let dep: MyProtocol; func run() { dep.doWork() } }Protocol Abstractions in Swift
Swift protocols can carry associated types, default implementations, and composition — ideal for rich abstractions.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
func set(_ key: Key, value: Value)
}Abstraction Granularity
Interface Segregation: prefer narrow protocols over fat ones so implementations only provide what callers need.
// Fat (avoid):
protocol DataStore {
func save(...); func load(...); func delete(...); func query(...); func migrate()
}
// Narrow (prefer):
protocol Readable { func load<T>(...) throws -> T }
protocol Writable { func save<T>(...) throws }DIP with Combine
Return AnyPublisher from protocol methods so the underlying publisher type remains hidden.
protocol UserStream {
func userUpdates() -> AnyPublisher<User, Never>
}DIP with async/await
Protocol methods marked async throws are fully compatible with Swift structured concurrency.
protocol Analytics {
func track(event: String) async
}
struct FirebaseAnalytics: Analytics {
func track(event: String) async { /* Firebase call */ }
}DIP at the App Level
Wire up concrete implementations at the app's entry point (Composition Root), keeping all binding in one place.
@main
struct MyApp: App {
let db: Database = SQLiteDatabase()
let repo: UserRepository = RemoteUserRepository(db: db)
let useCase = FetchUserUseCase(repo: repo)
var body: some Scene {
WindowGroup { ContentView(useCase: useCase) }
}
}Testing with DIP
In tests, substitute real implementations with mocks/stubs at the injection site.
let mockDB = InMemoryDatabase()
let service = ProfileService(db: mockDB)
// Exercise service
XCTAssertEqual(try mockDB.load(key: "profile"), expectedProfile)Quick Check
What does the Dependency Inversion Principle state about high-level modules?
Lesson Recap
DIP: high-level modules depend on protocol abstractions; concrete types implement them. Extract narrow protocols, inject via constructors, and wire everything at the Composition Root. This enables swapping implementations for testing, feature flagging, and future requirements.
Learn Swift with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 122
- Lessons
- 409
Frequently Asked Questions
Is the “Dependency Inversion with Protocol Abstractions” lesson free?
Yes — any 3 lessons of the Swift Academy learning path, including “Dependency Inversion with Protocol Abstractions”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Swift Academy course includes 4 lessons in total.
What will I learn in “Dependency Inversion with Protocol Abstractions”?
Depending on abstractions not concretions using Swift protocols as boundaries. 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, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.
How long does the “Dependency Inversion with Protocol Abstractions” 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
- Layers: Domain, Data and Presentation
- Use Case and Repository Patterns
- Dependency Inversion with Protocol Abstractions
- Wiring Layers Together without a DI Framework