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 [email protected]. I’d greatly appreciate your feedback! :)