What is Protocol-Oriented Programming in iOS and How It Differs from Object-Oriented Programming
If you've spent any time with Swift or iOS development, you've probably heard the term "Protocol-Oriented Programming" (POP). Apple introduced it as a new paradigm, a "first principle" of Swift. But what does that actually mean, especially if you come from an Object-Oriented Programming (OOP) background?
Let's break it down. This post will explain what POP is, how it stacks up against traditional OOP, and why it’s a powerful tool in your iOS development arsenal.
A Quick Recap of Object-Oriented Programming (OOP)
First, let's refresh our memory on OOP. It's a programming model built around the concept of objects. In Swift, these are typically represented by classes. OOP has a few core pillars:
- Encapsulation: Bundling data (properties) and the methods that operate on that data into a single unit (an object).
- Inheritance: Allowing a new class (subclass) to take on the properties and methods of an existing class (superclass).
- Polymorphism: Allowing an object to be treated as an instance of its parent class, enabling more flexible code.
Inheritance is a key feature. For example, you might model a Manager
as a specialized type of Employee
.
// A base class for an employee
class Employee {
var name: String
var hourlyWage: Double
init(name: String, hourlyWage: Double) {
self.name = name
self.hourlyWage = hourlyWage
}
func performWork() {
print("I'm working.")
}
}
// A subclass that inherits from Employee
class Manager: Employee {
var teamSize: Int
init(name: String, hourlyWage: Double, teamSize: Int) {
self.teamSize = teamSize
super.init(name: name, hourlyWage: hourlyWage)
}
// Overriding a method from the superclass
override func performWork() {
print("I'm managing my team of \(teamSize).")
}
}
let manager = Manager(name: "Jane Doe", hourlyWage: 50.0, teamSize: 10)
manager.performWork() // Prints: "I'm managing my team of 10."
This works well, but it has limitations, which is where POP comes in.
Introducing Protocol-Oriented Programming (POP)
Protocol-Oriented Programming is a paradigm that shifts the focus from what an object is (its class) to what an object can do (its capabilities).
In Swift, a protocol is like a blueprint. It defines a set of properties, methods, and other requirements that a type must implement. Think of it as a contract: if a type "adopts" or "conforms to" a protocol, it promises to provide the functionality defined in that protocol.
The core idea of POP is to build your application by defining these contracts (protocols) and then creating types (struct
, class
, or enum
) that fulfill them.
POP vs. OOP: The Key Differences
POP isn't meant to replace OOP entirely. Instead, it offers a more flexible and powerful alternative to some of OOP's more rigid patterns.
1. Inheritance vs. Composition
This is the biggest difference. OOP relies heavily on single inheritance, meaning a class can only inherit from one direct parent. This can lead to problems. What if you have a Bird
class and a Plane
class? Both can fly, but they don't share a logical parent. You can't just create a FlyingThing
superclass because a Bird
is an animal and a Plane
is a machine.
POP solves this with composition. A type can conform to multiple protocols at once. This allows you to build functionality by combining smaller, reusable pieces.
Let's model the flying example using protocols:
// Define a "capability" as a protocol
protocol Flyable {
var currentAltitude: Double { get set }
func takeOff()
func land()
}
// A Bird can fly
struct Bird: Flyable {
var currentAltitude: Double = 0
func takeOff() {
print("Flapping wings to take off!")
}
func land() {
print("Landing gracefully on a branch.")
}
}
// A Drone can also fly
class Drone: Flyable {
var currentAltitude: Double = 0
func takeOff() {
print("Powering on rotors to take off.")
}
func land() {
print("Deploying landing gear.")
}
}
Here, both Bird
and Drone
can Flyable
, but they don't need to share a common parent. We've described a shared capability without forcing a rigid class structure.
2. Value Types vs. Reference Types
OOP in Swift is often associated with classes
, which are reference types. When you pass a class instance around, you're passing a reference (or pointer) to the same object in memory. If you change it in one place, it changes everywhere.
POP works beautifully with structs
and enums
, which are value types. When you pass a value type, you pass a copy. This prevents unintended side effects and makes code easier to reason about, especially in multi-threaded applications.
// Struct (Value Type) - used with POP
struct Point {
var x: Int
}
var pointA = Point(x: 10)
var pointB = pointA // A copy is made
pointB.x = 20
print(pointA.x) // Prints: 10
print(pointB.x) // Prints: 20
// Class (Reference Type) - often used with OOP
class View {
var width: Int
init(width: Int) { self.width = width }
}
var viewA = View(width: 100)
var viewB = viewA // A reference is passed
viewB.width = 200
print(viewA.width) // Prints: 200
print(viewB.width) // Prints: 200
Because protocols can be adopted by both value and reference types, you get the freedom to choose the right tool for the job.
Real-World Usage in iOS Development
You're already using POP every day as an iOS developer, maybe without realizing it! The iOS SDK is filled with protocols.
-
Codable
: This is a perfect example. To make a custom type convertible to and from a data format like JSON, you don't inherit from aSerializable
class. Instead, you conform to theEncodable
andDecodable
protocols (or theCodable
typealias which combines both).struct Product: Codable { let id: UUID let name: String let price: Double }
-
UITableViewDataSource
: When you want to display data in a table view, yourUIViewController
doesn't need to be a subclass ofUITableView
. Instead, it conforms to theUITableViewDataSource
protocol. This protocol requires your controller to implement methods liketableView(_:numberOfRowsInSection:)
andtableView(_:cellForRowAt:)
. This is the delegate pattern, a cornerstone of iOS development and a prime example of POP in action. -
Equatable
&Hashable
: To check if two instances of your custom type are equal (using==
) or to store them in aSet
or use them asDictionary
keys, you simply conform to theEquatable
andHashable
protocols, respectively.
Which One Should You Use?
It's not a battle of POP vs. OOP. The best approach for modern Swift development is to use them together. They solve different problems.
A great rule of thumb is: "Start with a protocol, use a struct if you can, and a class when you must."
- Protocols are excellent for defining contracts and shared functionality.
- Structs are great for modeling data because their value-type nature makes your code safer and more predictable.
- Classes are still necessary when you need reference semantics, inheritance from an existing framework class (like
UIViewController
), or features likedeinit
.
By embracing a protocol-oriented mindset, you can write code that is more flexible, reusable, and easier to test. Happy coding!
Related Posts
I Built and Launched Readometry in 5 Days — Here's the Full Story
How I built and shipped Readometry, a reading habit tracker, in just 5 days — and what I learned in the process.
Why iOS Development is Perfect for 2025
Discover why iOS development stands out as the ideal stack for building focused, high-quality software in 2025.