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 ofItem
objects, representing the items in the cart. -
mutating func addItem(_ item: Item)
: This method allows adding an item to the cart. Themutating
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 provideditem
to theitems
array, effectively adding it to the cart. -
func calculateTotalPrice() -> Double
: This function iterates through theitems
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.