Mastering Swift's Development Blog

Follow Us On Twitter
  • Jon Hoffman

Swift Protocols Part 3: Protocol Extensions

Updated: Sep 4


Protocols are the core in which the Swift programming language is built upon. Understanding how they work and what makes them so powerful is essential to understanding the language itself. The protocol is so important to the Swift language that we have create a four-part series on it. In this post we will examine protocol extensions and how to effectively use them.


Protocol Extensions

Protocol extensions let us extend a protocol to provide functionality or property implementations for types that adopt the protocol. This eliminates the need for conforming types to reimplement common functionality. It also gives us the ability to add new functionality to types that adopt a protocol.


In this first example, let’s use the FullName protocol that we have used in the first two posts of the protocol series. The FullName protocol is defined like this:

protocol FullName {
    var firstName: String { get set }
    var lastName: String { get set }
    
    func getName() -> String
}

To define a protocol extension, we start off with the extension keyword followed by the name of the protocol we wish to extend as shown here

extension FullName {

}

In this example we are going to extend the FullName protocol and put in a default implementation of the getName() function. The following code shows the full implementation for the FullName protocol.

extension FullName {
    func getName() -> String {
        return firstName + " " + lastName
    }
}

With this extension every type that adopts the FullName protocol will, by default, receive this implementation of the getName() function. Any type that adopts this protocol has the ability to override this default implementation but it is no longer required for them to implement it.


If we implement something with a protocol extension, we do not need to include it within the protocol itself. In this case, once we implement the getName() function with the FullName protocol extension we can exclude it from the protocol. Therefore, we could define the FullName protocol, with the extension, like this.

protocol FullName {
    var firstName: String { get set }
    var lastName: String { get set }
}

extension FullName {
    func getName() -> String {
        return firstName + " " + lastName
    }
}

As we mentioned earlier, with this extension in place we are no longer required to implement the getName() function in types that adopt the FullName protocol. As an example, if we were to adopt this protocol by a concrete type named BasketballPlayer, we could implement it like this.

struct BasketballPlayer: FullName {
    var firstName: String
    var lastName: String
}

Another thing we can use protocol extensions for is to add additional functionality to existing types that adopt certain protocols. As an example the Swift’s Array type adopts requirements from the Collections protocol which has a requirement for the isEmpty property. What if we wanted to create an isNotEmpty property as well? We could very easily add this functionality to all types that adopt the Collection protocol, including Arrays, Dictionaries and Sets, like this.

extension Collection {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

We could then use this new property like this:

var myArray = [Int]()
myArray.isNotEmpty

We also have the ability to constrain our extensions. For example, what if we wanted to create an extension, for types that adopt the collection protocol, with a function to remove all duplicate entries. We would need to ensure that the elements in the collection implemented the Equatable protocol. To do this we could write the extension like this:

extension Collection where Elements: Equatable {
    func withoutDuplicates() -> [Element] {
        //code here
    }
}

Element is used by the AnyCollection protocol as the collection type and the Collection protocol inherits from the AnyCollection protocol. In this example, we use the where clause to add this extension to any collection whose elements conform to the Equatable protocol. The where clause enables us to add functionality to types where we may not know the exact types of data being stored, which is a very powerful tool.


A lot of times, when we are writing code, we mainly focus on getting the application to work and implementing functionality as defined by the current requirements. For the short term, that can be great however as we build up tech debt our code can become harder to maintain and a lot less flexible which will lead to much longer development times. We can avoid or at least minimize this tech debt cycle by taking the time to properly architect our applications in the beginning.


The examples in this post and the Swift Protocols Part 2: Protocol Inheritance and Composition illustrate the power of the protocol and demonstrate why it is best practice, as Apple has recommended, to start our designs with the protocol. These two posts also cover some of the pillars that the Protocol Oriented Design paradigm is built upon. Understanding Protocol Inheritance, composition and extensions is essential to designing easy to maintain and flexible code with the Swift programming language.


This is the third post of our four-part protocol series and was written to show how protocol Extensions work. We have one more post in this series which discusses associate types with protocols. Here is the list of other posts in this series:


Swift Protocols Part 1: Understanding the Protocol

Swift Protocols Part 2: Protocol Inheritance and Composition

Swift Protocols Part 4: Associated Types with Protocols


masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt