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 aSerializableclass. Instead, you conform to theEncodableandDecodableprotocols (or theCodabletypealias 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, yourUIViewControllerdoesn't need to be a subclass ofUITableView. Instead, it conforms to theUITableViewDataSourceprotocol. 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 aSetor use them asDictionarykeys, you simply conform to theEquatableandHashableprotocols, 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
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.
10 Things That Make Swift Truly Unique
Discover the features that make Swift stand out among programming languages.