Protocol-Oriented Programming in Swift Explained: How POP Works & Why It Matters
Dive into Protocol-Oriented Programming (POP) in Swift and learn how Apple wants you to rethink code reusability, flexibility, and…

🧠 Introduction: Classes Aren’t Everything Anymore
Remember when you first learned Swift and were told, “Everything starts with a class”?
Well, Apple had other plans. In WWDC 2015, they dropped a subtle bomb:
“We think Protocol-Oriented Programming is the future of Swift.”
Wait, what? Protocols? Those things we barely cared about in Objective-C? Now they’re the star of the show?
Exactly. Protocol-Oriented Programming (POP) isn’t just some buzzword — it’s Swift’s way of saying:
“You don’t need to build a kingdom of subclasses. You just need good behavior.”
In this article, we’ll break down:
- What Protocol-Oriented Programming really means in Swift
- How it compares to the old-school Object-Oriented Programming (OOP)
- When to use it (and when to maybe chill with it)
- Real-world code examples that’ll actually make sense
By the end, you’ll start thinking like a Swift engineer instead of a class hoarder 💡
Let’s go POP some assumptions 🧃
🧬 What Is Protocol-Oriented Programming (POP)?
At its core, Protocol-Oriented Programming is Swift’s way of saying:
“Don’t start by asking what something is. Start by asking what it does.”
In other words — behavior over blueprint. Instead of building everything around classes and inheritance (hello, 90s), Swift encourages you to define behavior in protocols, and then make your types conform to them.
🛠 Think of it like this:
If you were building a Bird, old-school OOP thinking says:
- Start with a base
Animalclass - Inherit a
FlyingAnimalsubclass - Then create your beautiful
Birdsubclass
💥 POP says: Nope.
Let’s just say:
- A
Birdcan fly →Flyable - It can make sounds →
SoundEmittable - It can lay eggs →
EggLaying
You now have a Bird struct or class that composes behavior using protocols — no deep inheritance tree needed. This is composition over inheritance, and Swift is built to thrive in it.
Why it feels natural in Swift
- Swift favors value types (
struct,enum) over classes. - Protocols +
struct= lightweight, thread-safe, and testable. - You can extend protocols to add default implementations (yes, like abstract base classes… but better).
In short: POP is about what your type _does_, not what it is derived from. And once that clicks? Your Swift code starts feeling clean, flexible, and oddly satisfying.
🧵 Why Swift Loves POP (Even If You Didn’t Notice)
Swift isn’t just flirting with Protocol-Oriented Programming — it’s fully committed. Under the hood, Apple’s own frameworks and Swift’s standard library are built on protocols more than classes.
Don’t believe me? Take a look at your favorite Swift types:
Equatable,Hashable,Codable→ ProtocolsSequence,Collection,Comparable→ Protocols- Even
AsyncSequence,View(in SwiftUI), andIdentifiable? Yup. Protocols.
These aren’t just tags or interfaces. These protocols often come with default behavior baked in, thanks to protocol extensions.
🍰 Protocol Extensions: The Secret Sauce
Protocol extensions let you:
- Provide default implementations of methods
- Add computed properties
- Add shared behavior across all conforming types
protocol Flyable {
func fly()
}
extension Flyable {
func fly() {
print("Whoosh! I’m flying ✈️")
}
}Now any type that conforms to Flyable can get flying power for free. That’s inheritance-like behavior, without the class-based baggage.
🧱 Value Types + Protocols = POP Superpower
Swift’s structs:
- Are copied on assignment (safe for concurrency)
- Have no need for deinitializers
- Can conform to protocols just like classes
POP fits beautifully with value types, making your code:
- More modular
- Easier to test
- Less error-prone (no surprise reference sharing!)
So, whether you’re building models, services, or even UI logic — protocols let you define behavior once and use it everywhere, without tying everything to a class hierarchy.
🧰 Key Tools in Protocol-Oriented Programming
Swift’s take on Protocol-Oriented Programming isn’t just about using protocol more often — it’s about how you use it. Here are the tools that make POP powerful in real-world Swift code:
1\. Protocols: The Behavior Blueprint
Protocols define what a type can do, without saying how it does it.
protocol Drivable {
func drive()
}This lets any type — a Car, a Truck, even a Robot — conform to Drivable and provide their own driving logic.
2\. Protocol Extensions: Default Behavior? Yes, Please.
This is where Swift flexes 💪 You can give default implementations inside protocol extensions, so every conforming type gets shared behavior for free.
extension Drivable {
func drive() {
print("Vroom vroom! 🚗")
}
}Now your Car can drive without writing a single extra line — unless you want to customize it.
3\. Composition Over Inheritance
Instead of subclassing a Vehicle superclass with hundreds of lines, you compose your types using small, focused protocols:
protocol HornBlowing {
func honk()
}
extension HornBlowing {
func honk() {
print("Beep beep! 🎺")
}
}
struct Scooter: Drivable, HornBlowing {}✅ Easy to test ✅ Easy to extend ✅ Easy to maintain
This makes your code cleaner and more modular — which is a huge win when building scalable apps or adopting Clean Architecture in iOS.
**Understanding Clean Architecture in iOS: A Beginner’s Guide** _Tired of Spaghetti Code? 🍝 Let’s Clean Things Up in iOS!_medium.comhttps://medium.com/swift-pal/understanding-clean-architecture-in-ios-a-beginners-guide-69d09b4883c4
4\. Associated Types (A Quick Nod)
Protocols can use associatedtype to remain generic and flexible.
protocol Repository {
associatedtype Item
func fetchAll() -> [Item]
}These are super useful in generic services — but they also make protocols harder to use as concrete types. We’ll stay out of the weeds here (but if you’re curious, I’ll gladly dive deeper later).
5\. Where Clauses: Get Specific, Swiftly
You can restrict default implementations to certain conditions using where.
extension Collection where Element: Equatable {
func containsDuplicates() -> Bool {
return count != Set(self).count
}
}This is like giving protocols a brain: “I’ll only do this if you meet these conditions.”
🧪 Real-World Example: Building a Reusable Downloader (The POP Way)
Let’s say you’re building an app that fetches different types of content — articles, images, maybe even cats in space 🐱🚀
Instead of writing a base Downloader class and subclassing the heck out of it, let’s use Protocol-Oriented Programming to keep things clean, flexible, and testable.
🧩 Step 1: Define the Behavior with a Protocol
We want something that can download anything.
protocol Downloadable {
associatedtype DataType: Decodable & Sendable
func fetch(from url: URL, completion: @escaping @Sendable(Result<DataType, Error>) -> Void)
}This is generic. It doesn’t care what it’s downloading — only that it can.
🧰 Step 2: Provide a Default Implementation (If Needed)
Let’s say most downloads use URLSession. We can give that logic to conforming types:
extension Downloadable where DataType: Decodable {
func fetch(from url: URL, completion: @escaping @Sendable (Result<DataType, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
return completion(.failure(error))
}
guard let data = data else {
return completion(.failure(NSError(domain: "No data", code: -1)))
}
do {
let decoded = try JSONDecoder().decode(DataType.self, from: data)
completion(.success(decoded))
} catch {
completion(.failure(error))
}
}.resume()
}
}shared logic, protocol-ified. You could now conform to Downloadable and customize behavior if needed.
📦 Step 3: Use It with Different Models
struct Article: Decodable {
let title: String
let content: String
}
struct ArticleService: Downloadable {
typealias DataType = [Article]
}And now:
let articleService = ArticleService()
let url = URL(string: "https://api.example.com/articles")!
articleService.fetch(from: url) { result in
switch result {
case .success(let articles):
print("Downloaded \(articles.count) articles")
case .failure(let error):
print("Oops: \(error)")
}
}🧪 Bonus: Easy to Mock for Testing
You can now create test stubs by just conforming to the protocol:
struct MockArticleService: Downloadable {
typealias DataType = [Article]
func fetch(completion: @escaping (Result<[Article], Error>) -> Void) {
completion(.success([
Article(title: "Mocked", content: "This is a test.")
]))
}
}Want to dive deeper into testability and mocking in Swift? You’ll love this: 👉 Dependency Injection in Swift — Beginner to Advanced Guide
**Dependency Injection in Swift: A Beginner-to-Advanced Guide** _🧪 Feeling tangled in singletons and tightly-coupled code? Learn how Dependency Injection in Swift can help you write…_medium.comhttps://medium.com/swift-pal/dependency-injection-in-swift-a-beginner-to-advanced-guide-b85378c6f8d2
Why This POP Approach Rocks
- No rigid base classes
- Behavior is shared and reusable
- Your types are decoupled and testable
- You can scale easily: want a
UserServiceorImageService? Just swap the model type
If you want to deep dive in URLSession, you can also read my most read article, 👉🏻 URLSession in Swift
**URLSession in Swift: Build a Clean and Testable Networking Layer** _Tired of tangled networking code in your iOS apps? Learn how to build a clean, testable, and scalable networking layer…_medium.comhttps://medium.com/swift-pal/urlsession-in-swift-build-a-clean-and-testable-networking-layer-261f96a3df63
🧨 Common Mistakes to Avoid in Protocol-Oriented Programming
Protocol-Oriented Programming is elegant… until it isn’t. When misused, it can lead to some pretty gnarly Swift code that even future-you won’t want to debug 😬
Here are some common traps to avoid:
❌ 1. Making Protocols Just to Say You’re Using POP
Some developers fall into the “Protocol Everything” mindset.
“I’ve made 14 protocols and 3 types for this… login button.”
If you’re creating protocols that are only conformed to by one type — and you’re not writing tests or reusing them — then you may just be adding unnecessary complexity.
👉 Ask yourself: Is this protocol helping me write better code, or am I just over-engineering?
❌ 2. Overcomplicating with Deep Protocol Chains
One of the things POP aims to solve is the painful hierarchy problem of OOP.
So if you start creating:
protocol A: B
protocol B: C
protocol C: DYou’ve just reinvented the inheritance problem… with extra steps 🧩
❌ 3. Assuming Protocols Are Always Better Than Classes
Not every use case is a protocol use case.
If you’re dealing with:
- Shared mutable state
- Identity semantics (e.g.,
===) - Object lifetime management
…then protocols with value types may not be the right choice. Don’t hesitate to use class when it’s the better tool for the job 🛠️
❌ 4. Forgetting Protocol Existentials Have Limitations
Ever tried doing this?
let service: Downloadable = ArticleService()Oops 😅 That won’t compile if your protocol has associatedtype. These are not usable as standalone types unless you use type erasure — which is advanced and often overkill.
Stick to concrete types or generics unless you know what you’re doing.
🎯 Final Thoughts: Protocols Are a Mindset Shift
Protocol-Oriented Programming isn’t about forcing everything into a protocol. It’s about changing how you think about design in Swift.
Instead of asking:
“What should this class inherit from?”
Start asking:
“What behaviors should this type have?”
By focusing on composition, flexibility, and reusable behavior, POP makes your code:
- Easier to test 🔍
- Safer to use with value types 🧱
- Less dependent on tangled class trees 🌳
But like all tools, POP shines when used wisely — not religiously. So next time you start a new feature, maybe don’t reach for BaseViewController. Reach for a protocol instead 👏
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read