iOS Development

What is Protocol-Oriented Programming in iOS and How It Differs from Object-Oriented Programming

·7 min read

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 a Serializable class. Instead, you conform to the Encodable and Decodable protocols (or the Codable 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, your UIViewController doesn't need to be a subclass of UITableView. Instead, it conforms to the UITableViewDataSource protocol. This protocol requires your controller to implement methods like tableView(_:numberOfRowsInSection:) and tableView(_: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 a Set or use them as Dictionary keys, you simply conform to the Equatable and Hashable 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 like deinit.

By embracing a protocol-oriented mindset, you can write code that is more flexible, reusable, and easier to test. Happy coding!

Related Posts