All articles
iOS10 min read

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…

K
Karan Pal
Author
Image generated by AI
Image generated by AI

🧠 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:

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:

💥 POP says: Nope.

Let’s just say:

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

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:

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:

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:

POP fits beautifully with value types, making your code:

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

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: D

You’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:

…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:

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! 🚀

● The newsletter

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