Skip to content

Swift Protocols — Protocol-Oriented Programming with Examples

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Swift Protocols. We cover key concepts, practical examples, and best practices to help you master this topic.

Swift protocols define a blueprint of properties, methods, and requirements that conforming types must implement, enabling polymorphic behavior across structs, classes, enums, and even other protocols without requiring a shared inheritance hierarchy.

What You'll Learn

  • Defining protocols with property and method requirements
  • Protocol conformance for structs, classes, and enums
  • Protocol inheritance and composition
  • Using protocols as types
  • Protocol-oriented design patterns

Why It Matters

Protocols are at the heart of Swift's standard library and Apple's frameworks. Codable, Equatable, Hashable, Comparable, Collection, View in SwiftUI — these are all protocols. Understanding protocols lets you write generic, reusable code that works with any type meeting your requirements, leading to more flexible and testable applications.

Real-World Use

The Codable protocol pair (Encodable and Decodable) enables automatic JSON Serialization and deserialization for any Swift type that conforms to it. Apps that fetch data from REST APIs use Codable to convert JSON responses into model objects instantly, without manual Parsing.

Learning Path

flowchart LR
  A[Inheritance
Lesson 12] --> B[Protocols
You are here] B --> C[Extensions
Lesson 14] B --> D[Generics
Lesson 15] style B fill:#f90,color:#fff

Defining a Protocol

A protocol declares requirements that conforming types must implement. Properties must specify whether they are gettable or gettable-and-settable.

protocol Identifiable {
    var id: String { get }
    var displayName: String { get }

    func describe() -> String
}

struct User: Identifiable {
    let id: String
    let username: String

    var displayName: String {
        return "@\(username)"
    }

    func describe() -> String {
        return "User \(displayName) (ID: \(id))"
    }
}

struct Product: Identifiable {
    let id: String
    let name: String
    let price: Double

    var displayName: String {
        return name
    }

    func describe() -> String {
        return "\(name) — $\(price)"
    }
}

let user = User(id: "abc123", username: "swiftdev")
let product = Product(id: "xyz789", name: "Keyboard", price: 99.99)
print(user.describe())
print(product.describe())

Output:

User @swiftdev (ID: abc123)
Keyboard — $99.99

Both User and Product conform to Identifiable but implement the requirements differently. The protocol guarantees that any Identifiable type has id, displayName, and describe().

Property Requirements

Protocols specify whether a property must be gettable or settable. Conforming types can satisfy these with stored or computed properties.

protocol FullyNamed {
    var fullName: String { get }
}

protocol SettableName {
    var name: String { get set }
}

struct Person: FullyNamed {
    let firstName: String
    let lastName: String

    var fullName: String {
        return "\(firstName) \(lastName)"
    }
}

class MutablePerson: SettableName {
    var name: String = ""

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

let person = Person(firstName: "Jane", lastName: "Doe")
print(person.fullName)
let mutable = MutablePerson(name: "John")
mutable.name = "Jonathan"
print(mutable.name)

Output:

Jane Doe
Jonathan

FullyNamed requires only a getter, so a computed property or a constant stored property suffices. SettableName requires get and set, so a variable stored property or a computed property with getter and setter is needed.

Method Requirements

Protocols can require instance methods, type methods, initializers, and subscripts.

protocol Playable {
    var duration: Double { get }
    mutating func play()
    mutating func pause()
    func durationFormatted() -> String
}

struct Song: Playable {
    let title: String
    let duration: Double
    private var isPlaying = false

    mutating func play() {
        isPlaying = true
        print("Playing: \(title)")
    }

    mutating func pause() {
        isPlaying = false
        print("Paused: \(title)")
    }

    func durationFormatted() -> String {
        let minutes = Int(duration) / 60
        let seconds = Int(duration) % 60
        return "\(minutes):\(String(format: "%02d", seconds))"
    }
}

var song = Song(title: "Bohemian Rhapsody", duration: 354.0)
song.play()
print("Duration: \(song.durationFormatted())")
song.pause()

Output:

Playing: Bohemian Rhapsody
Duration: 5:54
Paused: Bohemian Rhapsody

The mutating keyword in the protocol allows value types like structs to modify their properties. Classes can implement mutating methods without the mutating keyword.

Protocol Inheritance

A protocol can inherit from one or more other protocols, adding requirements on top of the inherited ones.

protocol Vehicle {
    var speed: Double { get set }
    func accelerate()
    func brake()
}

protocol ElectricVehicle: Vehicle {
    var batteryLevel: Double { get }
    func charge()
}

protocol SelfDriving: Vehicle {
    var autonomyLevel: Int { get }
    func enableAutopilot()
}

struct TeslaModel3: ElectricVehicle, SelfDriving {
    var speed: Double = 0
    var batteryLevel: Double = 0.8
    var autonomyLevel: Int = 2

    func accelerate() {
        speed += 10
        batteryLevel -= 0.01
    }

    func brake() {
        speed = max(0, speed - 10)
    }

    func charge() {
        batteryLevel = 1.0
        print("Charging complete")
    }

    func enableAutopilot() {
        if autonomyLevel >= 2 {
            print("Autopilot engaged")
        }
    }
}

var myCar = TeslaModel3()
myCar.accelerate()
print("Speed: \(myCar.speed), Battery: \(myCar.batteryLevel)")
myCar.enableAutopilot()

Output:

Speed: 10.0, Battery: 0.79
Autopilot engaged

ElectricVehicle inherits from Vehicle, so any type conforming to ElectricVehicle must satisfy both sets of requirements.

Protocol Composition

You can combine multiple protocols into a single requirement using the & operator.

protocol Namable {
    var name: String { get }
}

protocol Ageable {
    var age: Int { get }
}

func describe(_ item: Namable & Ageable) {
    print("\(item.name) is \(item.age) years old")
}

struct Pet: Namable, Ageable {
    let name: String
    let age: Int
}

let dog = Pet(name: "Max", age: 3)
describe(dog)

Output: Max is 3 years old

Protocol composition creates an anonymous protocol that requires conformance to all listed protocols. This is useful when you need a parameter to meet multiple requirements without creating a formal combined protocol.

Protocol Extensions

You can extend a protocol to provide default implementations of methods or computed properties.

protocol Greetable {
    var name: String { get }
    func greet() -> String
}

extension Greetable {
    func greet() -> String {
        return "Hello, \(name)!"
    }
}

struct EnglishSpeaker: Greetable {
    let name: String
}

struct FrenchSpeaker: Greetable {
    let name: String

    func greet() -> String {
        return "Bonjour, \(name)!"
    }
}

let english = EnglishSpeaker(name: "Alice")
let french = FrenchSpeaker(name: "Pierre")
print(english.greet())
print(french.greet())

Output:

Hello, Alice!
Bonjour, Pierre!

EnglishSpeaker uses the default implementation from the protocol extension, while FrenchSpeaker provides its own custom implementation. This is the backbone of Swift's protocol-oriented programming.

Using Protocols as Types

Protocols can be used as types in parameters, return values, collections, and variables.

protocol Describable {
    var summary: String { get }
}

struct Book: Describable {
    let title: String
    let author: String

    var summary: String {
        return "\(title) by \(author)"
    }
}

struct Movie: Describable {
    let title: String
    let director: String
    let year: Int

    var summary: String {
        return "\(title) (\(year)) directed by \(director)"
    }
}

let items: [Describable] = [
    Book(title: "1984", author: "George Orwell"),
    Movie(title: "Inception", director: "Christopher Nolan", year: 2010)
]

for item in items {
    print(item.summary)
}

Output:

1984 by George Orwell
Inception (2010) directed by Christopher Nolan

The heterogeneous array holds both structs because they all conform to Describable.

Common Mistakes

  1. Forgetting that protocols are only blueprints: Protocols cannot contain implementations by default. You must add protocol extensions for default implementations.

  2. Using class-only protocols excessively: Mark a protocol with AnyObject only when you specifically need reference semantics. Most protocols should work with value types too.

  3. Over-abstracting with tiny protocols: Having dozens of single-requirement protocols makes code hard to follow. Group related requirements into coherent protocols.

  4. Not using protocol composition: Instead of creating a combined protocol, use & composition in parameters. It is more flexible and reduces the number of named protocols.

  5. Confusing protocol inheritance with class inheritance: Protocol inheritance is additive — a conforming type must implement all requirements from all protocols in the chain. Class inheritance can selectively override.

Practice Questions

  1. How do you declare a protocol that requires a mutable property?
  2. Can a struct conform to a protocol? Can a protocol inherit from another protocol?
  3. What is the difference between protocol inheritance and protocol composition?
  4. How do protocol extensions provide default implementations?
  5. Challenge: Define a Sortable protocol with a value property and isGreaterThan(_:) method. Create structs Temperature and Price that conform to it. Write a generic function that sorts an array of Sortable items.

Mini Project

Build a Drawable protocol with:

  • func draw() -> String method
  • Protocol extension providing default "Drawing a shape" text
  • Conforming structs: Circle, Square, Triangle
  • An array of Drawable items and a loop that calls draw() on each
  • A ColoredDrawable protocol that inherits from Drawable and adds color: String

FAQ

Can a struct conform to a protocol?

Yes. Structs, classes, enums, and even other protocols can conform to protocols. This is what makes Swift protocol-oriented rather than class-oriented.

What is AnyObject protocol?

AnyObject is a protocol that restricts conformance to class types only. It is useful when you need reference semantics, weak references, or Objective-C interoperability.

Can I add a protocol conformance to a type I do not own?

Yes, using extensions. You can make Int, String, or any SDK type conform to your own protocols, as long as the protocol requirements do not conflict with existing implementations.

What happens if two protocols require the same method?

The conforming type provides a single implementation that satisfies both protocols, as long as the method signatures are identical.

How do I check protocol conformance at runtime?

Use the is and as? operators: if let describable = item as? Describable { print(describable.summary) }.

What's Next

Combine protocols with Extensions to add behavior to existing types, or explore Generics to write type-safe, reusable functions that work with any protocol-conforming type.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro