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.