Chapters

Hide chapters

Swift Apprentice: Fundamentals

First Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section III: Building Your Own Types

Section 3: 9 chapters
Show chapters Hide chapters

17. Protocols
Written by Ehab Amer

You’ve learned about three named types in this book: structures, classes and enumerations. There is another very special one: the protocol.

Unlike the other named types, protocols don’t define anything you instantiate directly. Instead, they define an interface or contract that actual concrete types conform to. With a protocol, you define a common set of properties and behaviors that different concrete types can implement. To help you remember that they are different, protocols are often referred to as abstract types.

You’ve been using protocols behind the scenes since the beginning of this book. In this chapter, you’ll learn the details about protocols and see why they’re central to Swift.

Introducing Protocols

You define a protocol much as you do any other named type. Start with this definition for a Vehicle:

protocol Vehicle {
  /// Return a description of the state of the vehicle.
  func describe() -> String

  // more to come...
}

Following the keyword, protocol is its name, followed by curly braces with the requirements inside. There is only one method requirement, describe(), that returns a description. The big difference you’ll notice is that the protocol doesn’t contain any implementation.

That means you can’t instantiate a Vehicle directly:

Delete that line and continue building the Vehicle abstraction. Add the following to the protocol body:

/// Increases speed until it reaches its maximum speed.
mutating func accelerate()

/// Stop moving. Reducing the speed to zero miles per hour.
mutating func stop()

You mark these methods mutating because when they are implemented, they need to change the instance’s state. You can also add a couple of property requirements:

/// The speed of the vehicle in miles per hour.
var speed: Double { get set }

/// The maximum speed attainable by this Vehicle type.
static var maxSpeed: Double { get }

When defining properties in a protocol, you must explicitly mark them as get or get set, similar to how you declare computed properties. However, much like methods, you don’t include any implementation for properties.

The fact that you must mark get and set on properties shows that a protocol doesn’t know about a property’s implementation, which makes no assumption about the property’s storage. You can implement these property requirements as computed properties or as regular variables. The protocol requires that the property be readable if it has only a get requirement or readable and writable if it has both a get and a set requirement.

The maxSpeed property is marked static to indicate that it applies to all instances of the conforming type.

In short, you use protocols to describe the requirements of a type. What you’ve defined here is the abstract idea of a vehicle.

Protocol Adoption

A protocol can be adopted by a class, struct or enum — and when another type adopts a protocol, it’s required to implement the methods and properties defined in the protocol. Once a type implements all members of a protocol, the type is said to conform to the protocol.

Here’s how you declare protocol conformance for your type. In the playground, define a new class that will conform to Vehicle:

class Unicycle: Vehicle {
}

You follow the name of the named type with a colon and the name of the protocol you want to adopt. This syntax might look familiar since it’s the same syntax you use to make a class inherit from another class. In this example, Unicycle conforms to the Vehicle protocol.

Since you haven’t fulfilled the requirements of the protocol, you get an error:

The fix-it button provided by Xcode is a quick way to add all the properties and methods you need to conform to the protocol. Fill in an implementation for Unicycle:

class Unicycle: Vehicle {
  func describe() -> String {
    "Unicycle @ \(speed) mph"
  }  
  func accelerate() {
    speed = min(speed + 2, Self.maxSpeed)
  }
  func stop() {
    speed = 0
  }
  var speed: Double = 0
  static var maxSpeed: Double { 15 }
}

With this implementation, Unicycle now conforms to Vehicle. For this type (a class), accelerate() and stop() don’t need to be marked mutating even though they modify the state of the instance.

Use a struct to define a Car:

struct Car {
  func describe() -> String {
    "Car @ \(speed) mph"
  }
  mutating func accelerate() {
    speed = min(speed + 20, Self.maxSpeed)
  }
  mutating func stop() {
    speed = 0
  }
  var speed: Double = 0
  static var maxSpeed: Double { 150 }
}

This Car type implements all of the methods required by Vehicle but does not conform to Vehicle. You must explicitly write that it conforms to the type to get conformance. You can do that by either adding it to the declaration as you did in with Unicycleor add conformance using an extension like this:

extension Car: Vehicle {}

With this code, Car now conforms to Vehicle. Since the definition of Car already contains everything it needs to be a Vehicle, the extension body is empty.

If you needed to, you could add code here to make it conform properly. This technique of re-opening the type helps adapt code you might not have the source code for. The fancy term for it is retroactive modeling.

Note: You can’t declare stored properties in extensions. You can only declare stored properties in the original type declaration or derived classes in the case of a class type. This limitation can present a challenge to implementing arbitrary protocol conformance for some types.

Defining Implementations

Protocol extensions allow you to define default implementation for protocol definitions. You might notice duplicated code in the example above. You can write a general purpose stop() method like so:

extension Vehicle {
  mutating func stop() {
    speed = 0
  }
}

Now when you conform to Vehicle, you don’t have to write an implementation for stop() if you are okay with this default one. However, if you need to do additional work, such as managing a fuel or peddling status, you can define your own stop() method in your conforming type.

You can also create extensions on protocols that aren’t part of the formal protocol definition, like this:

extension Vehicle {
  /// Return the speed as a value between 0-1.
  var normalizedSpeed: Double {
    speed / Self.maxSpeed
  }
}

Unlike an extension that implements a method or property part of the formal protocol, this implementation cannot be overridden by a conforming type. Every conforming type (Unicycle, Car, etc.) must accept this definition of normalizedSpeed.

Default Parameters

Protocols do not let you define default parameters like you can with functions or methods. But you can work around this limitation. To see how, create the following abstraction for Turnable types:

/// Different pressure options.
enum BrakePressure {
  case light
  case normal
  case hard
}

protocol Braking {
  /// Apply the brakes.
  mutating func brake(_ pressure: BrakePressure = .normal) // ERROR
}

You can simulate having a default argument like this:

protocol Braking {
  /// Apply the brakes.
  mutating func brake(_ pressure: BrakePressure)
}

extension Braking {
  /// Apply normal brakes.
  mutating func brake() {
    brake(.normal)
  }
}

The adopting type still needs to implement brake(_:) for all brake pressures, but gets access to a normal pressure brake() automatically.

Initializers in Protocols

While protocols themselves can’t be initialized, they can declare initializers that conforming types must implement:

protocol Account {
  var value: Double { get set }
  init(initialAmount: Double)
  init?(transferAccount: Account)
}

In the Account protocol above, you define two initializers as part of the protocol. Any type that conforms to Account is required to have these initializers. If you conform to a protocol with initializers using a class type, those initializers must use the required keyword:

class BitcoinAccount: Account {
  var value: Double
  required init(initialAmount: Double) {
    value = initialAmount
  }
  required init?(transferAccount: Account) {
    guard transferAccount.value > 0.0 else {
      return nil
    }
    value = transferAccount.value
  }
}

Using the required keyword will help you ensure that subclasses of your class also conform to the protocol. Swift is smart enough to realize that class marked final does not need to mark the initializers with required since there aren’t any subclasses.

To instantiate a BitcoinAccount, you can use BitcoinAccount(initialAmount: 30) like normal. However, to prove that you can create one strictly through the Account protocol, you can use a meta type of Account like so:

let accountType: Account.Type = BitcoinAccount.self
let account = accountType.init(initialAmount: 30)
let transferAccount = accountType.init(transferAccount: account)!

All types, including BitcoinAccount, have a property self that returns a descriptor to the type itself. The descriptor type is a so-called meta type and expressed as BitcoinAccount.Type. Since you know BitcoinAccount conforms to Account, you can assign it Account’s meta type, Account.Type. With this protocol meta type instance, you can use the init() methods directly to create new BitcoinAccount instances.

Protocol Inheritance

The Vehicle protocol contains a set of methods that could apply to any vehicle, such as a bike, car, snowmobile, or airplane!

You may wish to define a protocol that contains all the qualities of a Vehicle but is also specific to vehicles with wheels. For this, you can have protocols that inherit from other protocols, much like you can have classes that inherit from other classes:

protocol WheeledVehicle: Vehicle {
  var numberOfWheels: Int { get }
  var wheelSize: Double { get }
}

Any type you mark as conforming to the WheeledVehicle protocol will have all the members defined within the braces and the members of Vehicle. As with subclassing, any type you mark as a WheeledVehicle will have an “is-a” relationship with the protocol Vehicle.

You could extend Unicycle to be a WheeledVehicle like this:

extension Unicycle: WheeledVehicle {
  var numberOfWheels: Int { 1 }
  var wheelSize: Double { 20.0 }
}

Unicycle now conforms to to WheeledVehicle.

Using Protocols

Because each conforming type can supply its own implementation, protocols can be used with any type (structures, enumeration, classes) to achieve polymorphism like traditional base classes. Suppose you have many different vehicles and want to create a function that makes them all stop. You might try to implement it like this:

func stop(vehicles: [Vehicle]) {
  vehicles.forEach { vehicle in
    vehicle.stop() // ERROR: Cannot call a mutating method on a constant array!
  }
}

Because you marked stop() as mutating the compiler knows that this implementation will be problematic for non-reference conforming types like structs. To fix this, you can clarify what is being mutated:

func stop(vehicles: inout [Vehicle]) {
  vehicles.indices.forEach {
    vehicles[$0].stop()
  }
}

Notice that you’re not looping on the elements in the vehicles array directly. You’re looping on the indices then using that index to reach the vehicle in the array then call stop(). The inout keyword on vehicles makes the array modifiable so that calling a mutating method succeeds. You can get more information on inout in Chapter 5: “Functions”.

There is one more stylistic improvement you can make. In this code and the previous code, vehicles is an array of different types that all conform to the Vehicle protocol. In newer versions of Swift, you can distinguish between the protocol Vehicle and a box type that contains any kind of Vehicle. This is done by writing any Vehicle instead of just Vehicle. You can update the above function with this better style:

func stop(vehicles: inout [any Vehicle]) {
  vehicles.indices.forEach {
    vehicles[$0].stop()
  }
}

This use of any Vehicle makes it clear that vehicles is an array of existential box (any Vehicle) types. There is a small runtime cost to being able to work with all kinds of Vehicle types, and any Vehicle (while not required) highlights this cost. A future version of Swift may require you to use any here. Not doing so will likely become a warning or error.

Mini-Exercises

  1. Create an Area protocol that defines a read-only property area of type Double.
  2. Implement Area with structs representing Square, Triangle and Circle.
  3. Add a circle, a square and a triangle to an array. Compute the total area of shapes in the array.

Associated Types in Protocols

Some types are naturally associated together with others. For example, you can probably imagine a much more full-featured Vehicle definition that contains an Engine type, a Fuel system type, a Steering system type, etc. Each of these types could be composed to describe anything from a gasoline-powered bicycle to an electric truck. Swift gives you the power to do this.

You can add an associated type as a protocol member. When using associatedtype in a protocol, you’re simply stating there is a type used in this protocol without specifying what type this should be. It’s up to the protocol adopter to decide the exact type.

Rather than sticking with the Vehicle example, you can make a simple protocol to explore this feature.

protocol WeightCalculatable {
  associatedtype WeightType
  var weight: WeightType { get }
}

By defining the stand-in WeightType associated type, you delegate the decision of the type of weight to whatever adopts the protocol.

You can see how this works in the two examples below:

struct HeavyThing: WeightCalculatable {
  // This heavy thing only needs integer accuracy
  typealias WeightType = Int

  var weight: Int { 100 }
}

struct LightThing: WeightCalculatable {
  // This light thing needs decimal places
  typealias WeightType = Double

  var weight: Double { 0.0025 }
}

You use typealias in these examples to be explicit about the associated type. This explicitness usually isn’t required, as the compiler can often infer the type. In the previous examples, the type of weight clarifies what the associated type should be so that you can remove typealias.

You may have noticed that the contract of WeightCalculatable now changes depending on the choice of associated type in the adopting type.

Note that this prevents you from using the protocol as a simple variable type because the compiler doesn’t know what WeightType will be ahead of time. But it’ll recommend a solution for you:

If you press the fix button, Xcode will change the type from WeightCalculatable to any WeightCalculatable. For protocols that do not contain associated types, Swift doesn’t strictly require you to use the any keyword as you saw before. It is, however, required in protocols with associated types. This ensures you understand that the compiler is doing a lot for you to hide the size and implementation details of the underlying type with an existential type.

Implementing Multiple Protocols

A class can only inherit from a single class — this is the property of “single inheritance”. By contrast, a class, structure or enumeration can conform to as many protocols as you’d like! Suppose you made Wheeled a protocol instead of the WheeledVehicle protocol earlier. It might look like this:

protocol Wheeled {
  var numberOfWheels: Int { get }
  var wheelSize: Double { get }
}

You could conform Car to it like this with an extension:

extension Car: Wheeled {
  var numberOfWheels: Int { 4 }
  var wheelSize: Double { 17 }
}

Now Car conforms to both Vehicle and Wheeled. Protocols support multiple conformances. You can add any number of protocol conformances to classes, structures and enumerations. In the example above, the Car has to implement all members defined in all of the protocols it adopts.

Note: With a class that inherits from a base class and adopts many protocols, you must write the base class first in the list and then all the protocols it adopts.

Protocol Composition and some

In the previous section, you learned how to implement multiple protocols. Sometimes you need a function to take a data type that must conform to multiple protocols. That is where protocol composition comes in. Imagine you need a function that needs access to the Vehicle protocol’s mutable stop() function and the Wheeled protocol’s numberOfWheels property. You can do this using the & composition operator.

func freeze(transportation: inout any Vehicle & Wheeled) {
    transportation.stop()
    print("Stopping the rotation of \(transportation.numberOfWheels) wheel(s).")
}

You can call it like this:

var car: any Wheeled & Vehicle = Car()
freeze(transportation: &car)
// Stopping the rotation of 4 wheel(s).

You might be wondering why car needs to be any Wheeled & Vehicle. In order mutate a existential type, you must pass exactly that type. It wouldn’t be useful to pass a Car because the compiler would box it into a temporary any Wheeled & Vehicle mutate that box and then return leaving the original car value mysteriously untouched. Fortunately, trying to pass a Car type will result in a compiler error.

To fix this, instead of any Wheeled & Vehicle you can use some Wheeled & Vehicle. Unlike any which creates a existential box, the some keyword creates a generic function for every concrete type that is Wheeled & Vehicle.

func freeze(transportation: inout some Vehicle & Wheeled) {
    transportation.stop()
    print("Stopping the rotation of \(transportation.numberOfWheels) wheel(s).")
}

Now you can write it like this:

var car = Car()
freeze(transportation: &car)
// Stopping the rotation of 4 wheel(s).

freeze(transportation:) is a fully generic function! It turns out that protocols and generics are language features completely intertwined with one another. This relationship is why protocols are the basis for generic code. You will learn more about generics in the next chapter.

Requiring Reference Semantics

Protocols can be adopted by both value types (structs and enums) and reference types (such as classes), so you might wonder if protocols have reference or value semantics.

The truth is that it depends! If you have an instance of a class or struct assigned to a variable of a protocol type, it will express value or reference semantics that match the conforming type.

To illustrate, take the simple example of a Named protocol below, implemented as a struct and a class:

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

class ClassyName: Named {
  var name: String
  init(name: String) {
    self.name = name
  }
}

struct StructyName: Named {
  var name: String
}

If you were to assign a Named variable an instance of a reference type, you would see the behavior of reference semantics:

var named: Named = ClassyName(name: "Classy")
var copy = named

named.name = "Still Classy"
named.name // Still Classy
copy.name  // Still Classy

Likewise, if you assign an instance of a value type, you will see the behavior of value semantics:

named = StructyName(name: "Structy")
copy = named

named.name = "Still Structy?"
named.name // Still Structy?
copy.name  // Structy

The situation isn’t always this clear. You’ll notice that most of the time, Swift will favor value semantics over reference semantics. If you’re designing a protocol adopted exclusively by classes, it’s best to request that Swift uses reference semantics when using this protocol as a type.

protocol Named: AnyObject {
  var name: String { get set }
}

Using the AnyObject protocol constraint above indicates that only classes may adopt this protocol. This declaration makes it clear that Swift should use reference semantics.

The class keyword provides the same constraint. However, it is preferable to use the protocol AnyObject instead.

Note: You can learn more about the difference between value type and reference type semantics in the ”Value Types & Reference Types” chapter of Swift Apprentice: Beyond the Basics.

Protocols: More Than Bags of Syntax

As you have seen, protocols let you specify many syntax requirements for conforming types. However, they can’t (and never will) let you specify every conceivable requirement for the compiler to check. For example, a protocol may need to specify complexity requirements (O(1) vs. O(n)) for an operation, and it can do this only by stating it in comments. You need to understand all of these requirements that a protocol makes to conform correctly. This reality has led to the refrain that protocols are “more than bags of syntax” that the compiler can check. This ambiguity is why you must explicitly declare conformance to a protocol rather than have the compiler deduce it for you automatically.

Protocols in the Standard Library

The Swift standard library uses protocols extensively in ways that may surprise you. Understanding the roles protocols play in Swift can help you write clean, decoupled “Swifty” code.

Equatable

Some of the simplest code compares two integers with the == operator:

let a = 5
let b = 5

a == b // true

You can do the same thing with strings:

let swiftA = "Swift"
let swiftB = "Swift"

swiftA == swiftB // true

But you can’t use == on any type. Suppose you wrote a class to represent a team’s record and wanted to determine if two records were equal:

class Record {

  var wins: Int
  var losses: Int

  init(wins: Int, losses: Int) {
      self.wins = wins
      self.losses = losses
  }
}

let recordA = Record(wins: 10, losses: 5)
let recordB = Record(wins: 10, losses: 5)

recordA == recordB // Build error!

You can’t apply the == operator to the class you just defined. But the use of the equality operator isn’t simply “magic” reserved for standard Swift types like Int and String; they’re structs, just like Record. You can extend the use of this operator to your own types!

Both Int and String conform to the Equatable protocol from the standard library that defines a single static method:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

You can apply this protocol to Record like so:

extension Record: Equatable {
  static func ==(lhs: Record, rhs: Record) -> Bool {
    lhs.wins == rhs.wins &&
    lhs.losses == rhs.losses
  }
}

Here, you’re defining (or overloading) the == operator for comparing two Record instances. In this case, two records are equal if they have the same number of wins and losses.

Now, you’re able to use the == operator to compare two Record types, just like you can with String or Int:

recordA == recordB // true

Note: The compiler will often automatically write (or codegen) the function== for you. This automatic code generation happens for structures and enumerations that conform to Equatable. All stored properties and associated values must also be Equatable.

Comparable

A subprotocol of Equatable is Comparable:

protocol Comparable: Equatable {
  static func <(lhs: Self, rhs: Self) -> Bool
  static func <=(lhs: Self, rhs: Self) -> Bool
  static func >=(lhs: Self, rhs: Self) -> Bool
  static func >(lhs: Self, rhs: Self) -> Bool
}

In addition to the equality operator ==, Comparable requires you to overload the comparison operators <, <=, > and >= for your type. In practice, you’ll usually only provide <, as the standard library can implement <=, > and >= for you, using your implementations of == and <.

Now you can make Record adopt Comparable as shown below:

extension Record: Comparable {
  static func <(lhs: Record, rhs: Record) -> Bool {
    if lhs.wins == rhs.wins {
      return lhs.losses > rhs.losses
    }
    return lhs.wins < rhs.wins
  }
}

This implementation of < considers one record lesser than another record if the first record either has fewer wins than the second record or an equal number of wins but a greater number of losses.

“Free” Functions

While == and < are useful in their own right, the Swift library provides you with many “free” functions and methods for types that conform to Equatable and Comparable.

For any collection you define that contains a Comparable type, such as an Array, you have access to methods such as sort() that are part of the standard library:

let teamA = Record(wins: 14, losses: 11)
let teamB = Record(wins: 23, losses: 8)
let teamC = Record(wins: 23, losses: 9)
var leagueRecords = [teamA, teamB, teamC]

leagueRecords.sort()
// {wins 14, losses 11}
// {wins 23, losses 9}
// {wins 23, losses 8}

Since you’ve given Record the ability to compare two values, the standard library has all the information it needs to sort an array of Records! As you can see, implementing Comparable and Equatable gives you quite an arsenal of tools:

leagueRecords.max() // {wins 23, losses 8}
leagueRecords.min() // {wins 14, losses 11}
leagueRecords.starts(with: [teamA, teamC]) // true
leagueRecords.contains(teamA) // true

Other Useful Protocols

You’ll find a few essential protocols in the Swift standard library that are helpful in almost any project.

Hashable

The Hashable protocol, a subprotocol of Equatable, is required for any type you want to use as a key to a Dictionary. As with Equatable, the compiler will automatically code generate Hashable conformance for you, but you must do it yourself for reference types such as classes.

Hash values help you quickly find elements in a collection. For this to work, values considered equal by == must also have the same hash value. Because the number of hash values is limited, there’s a finite probability that non-equal values can have the same hash. The mathematics behind hash values is quite complex, but you can let Swift handle the details. Make sure that everything you include in the == comparison is combined using the hasher.

For example:

class Student {
  let email: String
  let firstName: String
  let lastName: String

  init(email: String, firstName: String, lastName: String) {
    self.email = email
    self.firstName = firstName
    self.lastName = lastName
  }
}

extension Student: Hashable {
  static func ==(lhs: Student, rhs: Student) -> Bool {
    lhs.email == rhs.email &&
    lhs.firstName == rhs.firstName &&
    lhs.lastName == rhs.lastName
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(email)
    hasher.combine(firstName)
    hasher.combine(lastName)
  }
}

You use email, firstName and lastName as the basis for equality. An exemplary implementation of hash would be to use all of these properties by combining them using the Hasher type passed in. The hasher does the heavy lifting of properly composing the values.

You can now use the Student type as the key in a Dictionary:

let john = Student(email: "johnny.appleseed@apple.com",
                   firstName: "Johnny",
                   lastName: "Appleseed")
let lockerMap = [john: "14B"]
Identifiable

The Identifiable protocol vends a unique id property. Specifically, Identifiable requires only a get property named id whose type must be Hashable.

For example, you could make Student identifiable like this:

extension Student: Identifiable {
  var id: String {
    email
  }
}

This implementation works because email is unique for each student. (If two students shared the same email address, it would not work.) Also, the id is of type String which is Hashable.

You would not want to use firstName to fulfill the id requirement because two or more students might have the same first name.

CustomStringConvertible

The convenient CustomStringConvertible protocol helps you log and debug instances.

When you call print() on an instance such as a Student, Swift prints a vague description:

print(john)
// Student

As if you didn’t already know that! The CustomStringConvertible protocol has only a description property requirement. This property customizes how the instance appears in print() statements and in the debugger:

protocol CustomStringConvertible {
  var description: String { get }
}

You can provide a more readable representation by adopting CustomStringConvertible on the Student type.

extension Student: CustomStringConvertible {
  var description: String {
    "\(firstName) \(lastName)"
  }
}
print(john)
// Johnny Appleseed

CustomDebugStringConvertible is similar to CustomStringConvertible: It behaves exactly like CustomStringConvertible except it also defines a debugDescription. Use CustomDebugStringConvertible and debugPrint() to print to the output only in debug configurations.

Challenge

Before moving on, here is a challenge to test your knowledge of protocols. It is best to try to solve it yourself, but, as always, a solution is available if you get stuck.

Challenge 1: Pet Shop Tasks

Create a collection of protocols for tasks at a pet shop with dogs, cats, fish and birds.

The pet shop duties include these tasks:

  • All pets need to be fed.
  • Pets that can fly need to be caged.
  • Pets that can swim need to be put in a tank.
  • Pets that walk need exercise.
  • Tanks and cages need to be cleaned occasionally.
  1. Create classes or structs for each animal and adopt the appropriate protocols. Feel free to simply use a print() statement for the method implementations.

  2. Create homogeneous arrays for animals that need to be fed, caged, cleaned, walked, and tanked. Add the appropriate animals to these arrays. The arrays should be declared using the protocol as the element type, for example, var caged: [Cageable]

  3. Write a loop that will perform the proper tasks (such as feed, cage and walk) on each array element.

Key Points

  • Protocols define a contract that classes, structs and enums can adopt.
  • Adopting a protocol requires a type to conform to the protocol by implementing all methods and properties of the protocol.
  • You must declare protocol conformance explicitly; it is not enough to implement all protocol requirements.
  • You can use extensions for protocol adoption and conformance.
  • If you create an extension on a protocol that isn’t declared in the protocol, conforming types cannot override the extension.
  • If you create an implementation in an extension declared in the protocol, conforming types can override the extension.
  • A type can adopt any number of protocols, which allows for a quasi-multiple inheritance not permitted through subclassing.
  • any Protocol creates an existential box type to access the underlying type similar to a class base class.
  • some Protocol creates generic access to a concrete type.
  • Protocols are the basis for creating generic code.
  • The Swift standard library uses protocols extensively. You can use many of them, such as Equatable and Hashable, with your own types.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.