Protocols & Extensions
Protocols and Extensions are core to Swift’s "Protocol-Oriented Programming" paradigm.
Protocols
A Protocol defines a blueprint of methods, properties, and other requirements. It doesn't provide the code itself, but it ensures that any type that adopts it will have certain features.
protocol Device {
// Property requirement ({ get set } means it must be readable and writable)
var id: String { get set }
// Initializer requirement
init(id: String)
// Standard method requirement
func turnOn()
// Mutating method requirement (needed if the method changes the instance itself)
mutating func reset()
}
struct Phone: Device {
var id: String
// Conforming to the initializer requirement
init(id: String) {
self.id = id
}
// Conforming to the method requirements
func turnOn() {
print("Phone \(id) is booting up.")
}
mutating func reset() {
id = "Default"
}
}Multiple Protocols
A single type can adopt as many protocols as it needs. You simply list them after the colon and separate them with commas.
struct SmartWatch: Device, Equatable, CustomStringConvertible {
// Implementation goes here
}Why use Protocols?
Adopting a protocol allows you to write generic code that works with any type as long as it conforms to the blueprint. For example, a function could accept a list of "Device" items and call turnOn() on all of them, regardless of whether they are Phones, Tablets, or Laptops.
Using Protocols as Types (some vs any)
When using a protocol as a parameter or a return type, Swift asks you to clarify how that protocol should be handled using the some or any keywords.
The some Keyword (Opaque Types)
Using some tells the compiler that the underlying type is completely fixed and will never change, even though the exact type name is hidden from the caller. It is highly efficient and is the standard way to return UI components in SwiftUI.
// The caller just knows it gets a Device back.
// The compiler guarantees it will ALWAYS be a Phone.
func createDevice() -> some Device {
return Phone(id: "123")
}The any Keyword (Existential Types)
Using any creates a flexible box that can hold literally any type conforming to the protocol. The underlying type inside that box can change while the app is running. This requires a bit more memory but is necessary when you need to mix entirely different types together.
// This array can hold a mix of Phones, Tablets, and Laptops
let myDevices: [any Device] = [Phone(id: "1"), Tablet(id: "2")]Extensions
Extensions add new functionality to an existing class, struct, enum, or protocol type. This includes types for which you do not have the original source code (like built-in types).
extension Int {
func squared() -> Int {
self * self
}
}
print(7.squared()) // 49Protocol Extensions
You can extend a protocol to provide a default implementation to all conforming types. This is incredibly powerful as it adds functionality to multiple types at once.
extension Device {
func displayID() {
print("Device ID is: \(id)")
}
}
// Usage Example:
var myPhone = Phone(id: "PHN-01")
myPhone.displayID() // "Device ID is: PHN-01" -> Phone got this method for free!Extensions vs Inheritance
While both allow you to add functionality, they serve different purposes:
| Feature | Inheritance (Classes Only) | Extensions (All Types) |
|---|---|---|
| Relationship | "Is-a": Creates a child subclass. | "Skills": Adds a new ability to existing type. |
| Storage | Can add new Stored Properties. | Cannot add Stored Properties. |
| Override | Can change parent behavior via override. | Can only add new behavior. |
NOTE
Extensions are the preferred way to grow your code in Swift. They keep things simple by adding "skills" without creating a messy "family tree" (inheritance).
Built-in Protocols
Swift has many powerful built-in protocols that you can adopt to make your custom types feel like native ones. Some of them are:
Equatable
Allows you to compare two instances using ==.
struct Point: Equatable {
var x: Int, y: Int
}
let a = Point(x: 1, y: 2)
let b = Point(x: 1, y: 2)
print(a == b) // trueComparable
Allows you to use operators like <, >, and sorting.
struct Score: Comparable {
var value: Int
static func < (lhs: Score, rhs: Score) -> Bool {
lhs.value < rhs.value
}
}
let high = Score(value: 100)
let low = Score(value: 50)
print(low < high) // trueHashable
Allows your type to be used as a key in a Dictionary or stored securely in a Set. Swift can automatically generate the background code for this as long as all the properties inside your type are also Hashable.
struct User: Hashable {
var username: String
}Codable
Allows your type to be easily converted to and from external data formats like JSON. Like Hashable, Swift almost always generates the required code for you automatically.
struct Player: Codable {
var name: String
var score: Int
}Identifiable
Requires your type to have a unique id property so that Swift can tell individual instances apart. This protocol is essential when building user interfaces with SwiftUI, as it allows lists to track exactly which items are added, removed, or changed.
import Foundation
struct Book: Identifiable {
let id = UUID() // Automatically generates a unique identifier
var title: String
}
let myBook = Book(title: "Treasure Island")
print(myBook.id) // e.g., 550E8400-E29B-41D4-A716-446655440000CustomStringConvertible
Allows you to customize how your type is printed in print() or converted to a String.
struct Pirate: CustomStringConvertible {
var name: String
var description: String { "Ahoy! I am \(name)." }
}
let p = Pirate(name: "Jack")
print(p) // "Ahoy! I am Jack."