Protocols in iOS Object-Oriented Programming

If you’re new to coding and diving into the world of Swift, one of the most exciting and versatile concepts you’ll encounter is protocols. Protocols are a fundamental building block of Swift’s object-oriented programming (OOP) model and can help you write cleaner, more modular, and more reusable code.

In this article, you’ll explore the power of protocols and how to use them to create flexible, adaptable, and robust Swift apps. By the end, you’ll have a solid understanding of protocols and be ready to put them into practice in your own projects. It’s time to get started!

What Are Protocols?

In Swift, a protocol is a blueprint that defines a set of properties, methods, and other requirements. Classes, structs, and enums can then “conform” to a protocol, which means they must implement the protocol’s requirements.

Protocols are like a contract – they specify what a conforming type must provide but don’t actually implement any of that functionality themselves. This separation of interface and implementation is one of the key benefits of protocols.

Here’s a simple example of a protocol in Swift:

import Foundation

protocol Nameable {
    var name: String { get set }
    func introduce()
}

struct Person: Nameable {
    var name: String
    
    func introduce() {
        print("Hello, my name is (name).")
    }
}

let tom = Person(name: "Tom")
tom.introduce() // Prints "Hello, my name is Tom."

In this example, you define a Nameable protocol that requires a name property, with both getter and setter, and an introduce method. You then create a Person struct that conforms to the Nameable protocol by implementing the required properties and methods.

By using a protocol, you’ve created a generic, reusable blueprint for any type that needs to be “nameable.” This makes your code more modular, flexible, and easier to maintain.

Protocols and Inheritance

One powerful feature of protocols in Swift is their ability to work seamlessly with inheritance. When a class inherits from another class, it automatically inherits all of the properties and methods of the superclass. But what if you want to add additional requirements to a subclass?

This is where protocols come in handy. Take a look at an example:

  
import Foundation

protocol Vehicle {
    var make: String { get }
    var model: String { get }
    func drive()
}

class Car: Vehicle {
    let make: String
    let model: String
    
    init(make: String, model: String) {
        self.make = make
        self.model = model
    }
    
    func drive() {
        print("Driving the (make) (model).")
    }
}

class ElectricCar: Car, Chargeable {
    func charge() {
        print("Charging the (make) (model).")
    }
}

protocol Chargeable {
    func charge()
}

In this example, you have a Vehicle protocol that defines the basic properties and methods of a vehicle. The Car  class conforms to the Vehicle protocol and provides the required implementations.

You then create a new ElectricCar  class that inherits from Car  and also conforms to a new Charcheable protocol. This lets you add the charge()  method to the ElectricCar class without modifying the Car  class.

By combining inheritance and protocols, you’ve created a flexible and extensible class hierarchy that can easily accommodate new requirements and behaviors.

Putting it Into Practice

Now that you understand protocols, it’s time to put them into practice with a sample app. You’ll create a basic shopping cart system that demonstrates the power of protocols.

Open up a new Apple Playground and get started! If you don’t have Apple Playgrounds, you can download it here: https://developer.apple.com/swift-playgrounds/ 

import Foundation

protocol Item {
  var name: String { get set }
  var price: Double { get set }
}

// Physical Item Struct (conforms to Item)
struct PhysicalItem: Item {
  var name: String
  var price: Double
  let weightInGrams: Int
}

// Digital Item Struct (conforms to Item)
struct DigitalItem: Item {
  var name: String
  var price: Double
  let downloadSize: String
}

// ShoppingCart Protocol
protocol ShoppingCart {
  var items: [Item] { get set }
  mutating func addItem(_ item: Item)
  func calculateTotalPrice() -> Double
}


struct BasicCart: ShoppingCart {

  var items: [Item] = []

mutating func addItem(_ item: Item) { 
    items.append(item)
  }

  func calculateTotalPrice() -> Double {
    var total = 0.0
    for item in items {
      total += item.price
    }
    return total
  }
}

// Usage Example
var cart = BasicCart()

let milk = PhysicalItem(name: "Milk", price: 2.99, weightInGrams: 946)
let ebook = DigitalItem(name: "Swift Programming Guide", price: 9.99, downloadSize: "10MB")

cart.addItem(milk)
cart.addItem(ebook)

let totalPrice = cart.calculateTotalPrice()
print("Total price: $(totalPrice)") // Prints "Total price: $12.98"

This example demonstrates how to create a basic shopping cart system in Swift using protocols and structs. Here’s a breakdown of the code:

Defining the Item Protocol:

You start by defining a protocol named Item. This protocol acts as a blueprint for any item that can be added to the shopping cart. It specifies two properties that all items must have: name, a string, and price, a double.

Creating Item Structs:

Next, you create two structs, PhysicalItem and DigitalItem, which conform to the Item protocol. PhysicalItem represents a physical product with an additional property, weightInGrams. DigitalItem represents a digital product with a downloadSize property. Both structs inherit the name and price properties from the Item protocol.

Designing the ShoppingCart Protocol:

The ShoppingCart protocol outlines the functionalities needed to manage a collection of items in the cart. It defines three properties and methods:

  • var items: [Item] { get set }: This property stores an array of Itemobjects, representing the items in the cart.
  • mutating func addItem(_ item: Item): This method allows adding an item to the cart. The mutating keyword indicates that this method modifies the cart’s state by adding an item.
  • func calculateTotalPrice() -> Double: This method calculates the total price of all items in the cart based on their individual prices.

Implementing the BasicCart Struct:

The BasicCart struct implements the ShoppingCart protocol, providing the concrete functionality for managing the cart.

  • var items: [Item] = []: This initializes an empty array to store the items added to the cart.
  • mutating func addItem(_ item: Item): This function appends the provided item to the items array, effectively adding it to the cart.
  • func calculateTotalPrice() -> Double: This function iterates through the items array, accumulates the prices of all items, and returns the total price.

Usage Example:

The code demonstrates how to use the BasicCart struct in practice. You first create a BasicCart instance called cart. Then, you create two item objects: milk, a PhysicalItem, and ebook, a DigitalItem. You add both items to the cart using the addItem method. Finally, you call the calculateTotalPrice method to get the total price of all items in the cart and print it to the console.