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:
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:
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.
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.
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:
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:
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:
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:
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:
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
- Create a
Playable
protocol with aplay()
method and have different types likeSong
,Video
, andGame
conform to it. - Define a
Shape
protocol with anarea()
method and implement it forRectangle
,Circle
, andTriangle
structs. - Create a protocol called
Stackable
withpush(_:)
andpop()
methods, then implement aStack
type that conforms to it. - 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! :)