Skip to main content

Swift Protocols Basics

Introduction​

Protocols are a powerful feature of the Swift programming language that define a blueprint of methods, properties, and other requirements. Think of a protocol as a contract: any type that conforms to a protocol guarantees that it will provide implementations for the requirements specified by that protocol.

In this tutorial, you'll learn:

  • What protocols are and why they're important
  • How to define and adopt protocols
  • How to implement protocol requirements
  • How protocols enable flexible, modular code design

What is a Protocol?​

A protocol defines a set of methods, properties, and other requirements that suit a particular task or functionality. It doesn't provide an implementation for any of these requirements—it only describes what an implementation must include.

When a class, struct, or enum adopts a protocol, it must provide implementations for all the requirements of that protocol.

Defining a Protocol​

Let's start by defining a simple protocol:

swift
protocol Describable {
var description: String { get }
func identify()
}

This protocol, called Describable, requires any conforming type to have:

  • A description property that can be read (indicated by { get })
  • An identify() method

Adopting a Protocol​

To adopt a protocol, you place its name after the type name, separated by a colon:

swift
struct Person: Describable {
let name: String
let age: Int

var description: String {
return "\(name), \(age) years old"
}

func identify() {
print("I am \(name), and I am \(age) years old.")
}
}

// Using the Person struct
let john = Person(name: "John", age: 30)
print(john.description)
// Output: "John, 30 years old"
john.identify()
// Output: "I am John, and I am 30 years old."

In this example, the Person struct conforms to the Describable protocol by implementing both the description property and the identify() method.

Property Requirements​

Protocol property requirements specify the property name and type. They also specify whether the property should be gettable (read-only) or both gettable and settable.

swift
protocol Vehicle {
var numberOfWheels: Int { get }
var description: String { get set }
}

struct Car: Vehicle {
let numberOfWheels: Int = 4 // This is read-only
var description: String // This is read-write
}

let myCar = Car(description: "A nice sedan")
print("My car has \(myCar.numberOfWheels) wheels")
// Output: "My car has 4 wheels"

Method Requirements​

Protocol method requirements specify the method name, parameters, and return type. You don't provide a method body in a protocol—that's done by the conforming types.

swift
protocol Calculator {
func add(_ a: Int, _ b: Int) -> Int
func subtract(_ a: Int, _ b: Int) -> Int
}

class BasicCalculator: Calculator {
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}

func subtract(_ a: Int, _ b: Int) -> Int {
return a - b
}
}

let calc = BasicCalculator()
print("5 + 3 = \(calc.add(5, 3))")
// Output: "5 + 3 = 8"
print("5 - 3 = \(calc.subtract(5, 3))")
// Output: "5 - 3 = 2"

Initializer Requirements​

Protocols can require specific initializers to be implemented:

swift
protocol Named {
init(name: String)
}

class Person: Named {
var name: String

required init(name: String) {
self.name = name
}
}

Note the required keyword. This ensures that all subclasses of Person also conform to the Named protocol by implementing the required initializer.

Protocol Composition​

Swift allows a type to conform to multiple protocols. This is done by listing the protocols, separated by commas:

swift
protocol Flyable {
func fly()
}

protocol Swimmable {
func swim()
}

struct Duck: Flyable, Swimmable {
func fly() {
print("The duck is flying")
}

func swim() {
print("The duck is swimming")
}
}

let donald = Duck()
donald.fly()
// Output: "The duck is flying"
donald.swim()
// Output: "The duck is swimming"

Real-World Example: Data Source Protocol​

Let's explore a real-world example of using protocols by creating a simplified data source system similar to what you might find in iOS development:

swift
protocol DataSource {
var count: Int { get }
func data(at index: Int) -> String
}

// A simple array-backed data source
class ArrayDataSource: DataSource {
private let items: [String]

init(items: [String]) {
self.items = items
}

var count: Int {
return items.count
}

func data(at index: Int) -> String {
return items[index]
}
}

// A class that uses a data source
class DataDisplayer {
let dataSource: DataSource

init(dataSource: DataSource) {
self.dataSource = dataSource
}

func displayAllData() {
for i in 0..<dataSource.count {
print("Item \(i): \(dataSource.data(at: i))")
}
}
}

// Creating and using these components
let fruitData = ArrayDataSource(items: ["Apple", "Banana", "Orange"])
let displayer = DataDisplayer(dataSource: fruitData)
displayer.displayAllData()
// Output:
// "Item 0: Apple"
// "Item 1: Banana"
// "Item 2: Orange"

This pattern is powerful because we could create other types that conform to DataSource (like a database-backed data source or a network-fetching data source), and the DataDisplayer class would work with any of them without changes.

Protocol Extensions​

Swift allows you to extend protocols to provide default implementations of methods and computed properties:

swift
protocol Greetable {
var greeting: String { get }
func greet()
}

extension Greetable {
// Default implementation
func greet() {
print(greeting)
}
}

struct EnglishGreeter: Greetable {
var greeting: String = "Hello!"
// No need to implement greet() since it's provided by the extension
}

struct SpanishGreeter: Greetable {
var greeting: String = "¡Hola!"
// Can override the default if needed
func greet() {
print("\(greeting) ¿Cómo estás?")
}
}

let english = EnglishGreeter()
english.greet()
// Output: "Hello!"

let spanish = SpanishGreeter()
spanish.greet()
// Output: "¡Hola! ¿Cómo estás?"

Protocol Types​

You can use a protocol as a type. This means you can write functions that accept any type that conforms to a specific protocol:

swift
func printDescription(of item: Describable) {
print(item.description)
item.identify()
}

let person = Person(name: "Emma", age: 25)
printDescription(of: person)
// Output:
// "Emma, 25 years old"
// "I am Emma, and I am 25 years old."

Summary​

Protocols are a fundamental building block of Swift that enable:

  • Clear contracts: They define what functionality types must implement
  • Polymorphism: You can write code that works with different types as long as they conform to the same protocol
  • Composition: Types can adopt multiple protocols, allowing for flexible code organization
  • Extensibility: Protocol extensions provide default implementations

By learning how to use protocols effectively, you'll be able to design more flexible, modular, and reusable Swift code.

Exercises​

  1. Create a Playable protocol with a play() method and have different types like Song, Video, and Game conform to it.
  2. Define a Shape protocol with an area() method and implement it for Rectangle, Circle, and Triangle structs.
  3. Create a protocol called Stackable with push(_:) and pop() methods, then implement a Stack type that conforms to it.
  4. Implement a protocol for logging that can be adopted by different classes in an app.

Further Reading​

Happy coding!



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)